Compare commits

..
48 Commits
Author SHA1 Message Date
Kristian S. Stangeland d5028b584d Put the provided CGLib dependency in the com.comphenix namespace. 2012-10-17 06:57:05 +02:00
Kristian S. Stangeland 2b6a4570ab Added a couple of extra constructors. 2012-10-17 05:58:47 +02:00
Kristian S. Stangeland 4717cca2ec Increment to 1.4.0. 2012-10-17 05:32:46 +02:00
Kristian S. Stangeland dd5303ea88 Use a boolean lookup table instead of a ConcurrentHashMap. 2012-10-17 04:13:25 +02:00
Kristian S. Stangeland fee9a32e4b Store a current player injection hook for each game phase.
That way, one failing the other won't cause any problems.
2012-10-17 03:33:49 +02:00
Kristian S. Stangeland 3a8a4d15cf Fixed a couple of small bugs. 2012-10-17 01:19:06 +02:00
Kristian S. Stangeland 4cd5d04cae Wait 5 seconds before unhooking every player in the server. 2012-10-17 00:46:51 +02:00
Kristian S. Stangeland 476a918794 Dynamically add or remove injected hooks depending on the listeners.
This occurs whenever a listener is added or removed. A listener can
now specify whether or not it's listening for packets sent BEFORE
a player has logged in (every packet upto Packet1Login and a few more),
or AFTER. By default, listeners only receive notifcation of packets
sent and received after. 

ProtocolLib will now only hook NetLoginHandler if there's a login 
listener, and vice versa. Thus, the new login feature will only
tax the server if another plugin is using it. In addition, ProtocolLib
will not consume any resources when it's not serving any listeners.
2012-10-16 22:24:30 +02:00
Kristian S. Stangeland ecdc9b4b6c Let's be generous and clean up the injection before the other plugins. 2012-10-16 17:01:34 +02:00
Kristian S. Stangeland 94efb38324 Retrieve the updated player entity as fast as we can. 2012-10-16 16:42:27 +02:00
Kristian S. Stangeland 09348343dd NetLoginInjector should not be public. 2012-10-16 08:01:04 +02:00
Kristian S. Stangeland d88f507337 Removed debug listener. 2012-10-16 07:59:03 +02:00
Kristian S. Stangeland b52670c4b3 Switching to overriding an ArrayList directly.
This makes the injection code compatible with earlier versions of
Minecraft (even 1.0.0).
2012-10-16 07:58:30 +02:00
Kristian S. Stangeland 93468b53b3 Massive update that adds support for intercepting packets during login.
This is achieved by injecting the NetLoginHandler when it's added
to the DedicatedServerConnectionThread's list of current login handlers.

PacketEvents during this phase uses "fake" Player objects that only
support a subset of methods. Consumers can expect the following methods
to be functional:
 * getPlayer()
 * getAddress()
 * getServer()
 * chat(String)
 * sendMessage(String)
 * sendMessage(String[])
 * kickPlayer(String)

A "fake" Player object can be converted to its real counterpart by
calling getPlayer().
2012-10-16 07:28:54 +02:00
Kristian S. Stangeland 9b4b161602 First test of the pre-login injector. 2012-10-15 00:31:55 +02:00
Kristian S. Stangeland 39805c5502 Incremented version to 1.3.3 2012-10-13 22:19:07 +02:00
Kristian S. Stangeland 98ae5c6e29 Added a monitor every packet listener. 2012-10-13 22:19:01 +02:00
Kristian S. Stangeland a173824b3f Switching to the public repository group. 2012-10-13 18:07:02 +02:00
Kristian S. Stangeland 51c5a23c83 Switching to the "repo" virtual website instead. 2012-10-13 17:19:45 +02:00
Kristian S. Stangeland 61cd8a3ae4 Using a self-hosted repository for now. 2012-10-12 04:09:38 +02:00
Kristian S. Stangeland fe3a69bd3d Switching from UTF-8 to cp1252. 2012-10-12 00:49:47 +02:00
Kristian S. Stangeland 8e70a56768 CGlib must be in the compile scope, otherwise it's not included.
In addition, updated the README with information about Maven.
2012-10-12 00:24:15 +02:00
Kristian S. Stangeland 28fc096740 Bumping to version 1.3.2 2012-10-12 00:04:59 +02:00
Kristian S. Stangeland 129687772f Mark CraftBukkit and cglib as provided.
CraftBukkit, or just Bukkit, will already be a dependecy for any
plugin. CGlib will be provided by ProtocolLib, so that's not needed
either.
2012-10-12 00:04:05 +02:00
Kristian S. Stangeland 768d169f27 Added the same NULL check to the generic Bukkit unwrapper. 2012-10-12 00:01:05 +02:00
Kristian S. Stangeland 57add8e26f Handling the case where someone is writing a NULL element to
a equivalent converter.
2012-10-11 23:57:45 +02:00
Kristian S. Stangeland 8bd7f75a6d Updating POM to use Sonatype Nexus OSS. 2012-10-11 22:30:46 +02:00
Kristian S. Stangeland 3af9196740 Converted ItemDisguise to a maven project. 2012-10-11 09:09:42 +02:00
Kristian S. Stangeland ff0cd06b2e [maven-release-plugin] prepare release ProtocolLib-1.3.1 2012-10-11 08:03:59 +02:00
Kristian S. Stangeland 97a22a0f6d Revert to SNAPSHOT due to buggy maven. 2012-10-11 08:03:45 +02:00
Kristian S. Stangeland 5a8847a719 Updated POM to use my personal maven repo. 2012-10-11 08:00:00 +02:00
Kristian S. Stangeland 707b4511ce Add repository information to POM. 2012-10-11 02:56:45 +02:00
Kristian S. Stangeland b4d0c4a36d Prepare POM for possibly being on the public maven repository. 2012-10-11 02:17:45 +02:00
Kristian S. Stangeland 8d3c1de9b9 Adding GPL v2 license information to every file. 2012-10-10 22:18:11 +02:00
Kristian S. Stangeland dc75f34e11 Add a warning message for lost packets. 2012-10-10 22:12:29 +02:00
Kristian S. Stangeland 5bda655f39 Switching to Maven instead of Ant. 2012-10-10 21:35:55 +02:00
Kristian S. Stangeland 4301dc6525 Add the ability to customize the worker threads. 2012-10-10 07:16:01 +02:00
Kristian S. Stangeland 17b7526fe9 Set the queued index before enqueuing, not after. 2012-10-10 06:00:42 +02:00
Kristian S. Stangeland cf68d229b0 Honor the sending index when the packet has finished processing. 2012-10-10 05:42:45 +02:00
Kristian S. Stangeland 7ed0bc82dd Make it possible for threads to delay packet transmission.
Threads can now increment a shared counter indicating that a packet
should not be transmitted after the default packet listener 
processing. This can be useful if a packet listener needs information
from additional packets before it can complete.

Packet listeners that whish to use this method begin by calling
incrementPacketDelay(). It is then responsible for calling
signalPacketTransmission() when it's done waiting. All processing
on PacketEvents outside packet listeners must be synchronized
with getProcessingLock().
2012-10-10 04:41:07 +02:00
Kristian S. Stangeland 20792aa09a Reorder injection hooks.
We put the network manager hook to the bottom as it's the least
likely version to be compatible with other plugins.
2012-10-10 02:50:27 +02:00
Kristian S. Stangeland eb328c41e8 Increase the player listener to MONITOR. 2012-10-10 02:48:24 +02:00
Kristian S. Stangeland 6053b9e64b Added the ability to determine if any client or server packet is valid.
Also, added a warning when a plugin attempts to listen for a packet
ID that doesn't exist in the current Minecraft version.
2012-10-10 02:41:34 +02:00
Kristian S. Stangeland f8bd36bf3c Added the ability to enumerate IntEnums. 2012-10-09 22:37:33 +02:00
Kristian S. Stangeland cecab6a169 Unwrap collections in the packet constructor. 2012-10-09 17:41:37 +02:00
Kristian S. Stangeland 2b90acf53e Ensure that non-CraftBukkit item stacks are correctly converted. 2012-10-09 16:48:55 +02:00
Kristian S. Stangeland 5e36547aa2 Give automatically created worker threads sensible names. 2012-10-09 00:07:40 +02:00
Kristian S. Stangeland 12eab67db7 Fixed JavaDoc. 2012-10-05 04:43:41 +02:00
106 changed files with 17462 additions and 13736 deletions

No files matched your search

+27 -8
View File
@@ -1,8 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="lib" path="D:/Games/Minecraft/Server Mods/API/bukkit-1.3.1-R2.0.jar"/>
<classpathentry kind="lib" path="/ProtocolLib/bin/ProtocolLib.jar"/>
<classpathentry kind="output" path="class"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry including="**/*.java" kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/ProtocolLib"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
+1
View File
@@ -0,0 +1 @@
target/
+23 -17
View File
@@ -1,17 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ItemDisguise</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ItemDisguise</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -1,11 +1,12 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.7
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.6
@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
+72
View File
@@ -0,0 +1,72 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.itemdisguise</groupId>
<artifactId>ItemDisguise</artifactId>
<version>1.0.0</version>
<name>Item Disguise</name>
<description>Change the appearance of inventory items.</description>
<properties>
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>bukkit-rep</id>
<url>http://repo.bukkit.org/content/groups/public</url>
</repository>
<repository>
<id>comphenix-rep</id>
<name>Comphenix Maven Releases</name>
<url>http://repo.comphenix.net/content/groups/public</url>
</repository>
</repositories>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<directory>src/main/java</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<scm>
<connection>scm:git:git://github.com/aadnk/ProtocolLib.git</connection>
<developerConnection>scm:git:git@github.com:aadnk/ProtocolLib.git</developerConnection>
<url>https://github.com/aadnk/ProtocolLib</url>
</scm>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
<url>http://www.gnu.org/licenses/gpl-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.3.2-R1.0</version>
</dependency>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
</project>
@@ -1,119 +1,122 @@
/*
* ItemDisguise - A simple Bukkit plugin that illustrates how to use ProtocolLib.
* 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.itemdisguise;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Server;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.FieldAccessException;
public class HideEnchantmentsListener {
private final Server server;
private final Logger logger;
public HideEnchantmentsListener(Server server, Logger logger) {
this.server = server;
this.logger = logger;
}
public void addListener(ProtocolManager protocolManager, JavaPlugin plugin) {
// Hide all enchantments
protocolManager.addPacketListener(new PacketAdapter(plugin, ConnectionSide.SERVER_SIDE, 0x67, 0x68, 0x3E) {
@Override
public void onPacketSending(PacketEvent event) {
PacketContainer packet = event.getPacket();
try {
// Item packets
switch (event.getPacketID()) {
case 0x67: // Set slot
removeEnchantments(packet.getItemModifier().read(0));
break;
case 0x68: // Set Window Items
ItemStack[] elements = packet.getItemArrayModifier().read(0);
for (int i = 0; i < elements.length; i++) {
if (elements[i] != null) {
removeEnchantments(elements[i]);
}
}
break;
case 0x3E: // Sound effect
event.setCancelled(true);
break;
}
} catch (FieldAccessException e) {
logger.log(Level.SEVERE, "Couldn't access field.", e);
}
}
});
// Censor
protocolManager.addPacketListener(new PacketAdapter(plugin, ConnectionSide.CLIENT_SIDE, 0x3) {
@Override
public void onPacketReceiving(PacketEvent event) {
if (event.getPacketID() == 0x3) {
try {
String message = event.getPacket().
getSpecificModifier(String.class).read(0);
if (message.contains("shit") || message.contains("fuck")) {
event.setCancelled(true);
event.getPlayer().sendMessage("Bad manners!");
}
} catch (FieldAccessException e) {
logger.log(Level.SEVERE, "Couldn't access field.", e);
}
}
}
});
}
public Server getServer() {
return server;
}
public void removeListener(ProtocolManager protocolManager, JavaPlugin plugin) {
// Just remove every adapter with this plugin
protocolManager.removePacketListeners(plugin);
}
private void removeEnchantments(ItemStack stack) {
Object[] copy = stack.getEnchantments().keySet().toArray();
for (Object enchantment : copy) {
stack.removeEnchantment((Enchantment) enchantment);
}
}
}
/*
* ItemDisguise - A simple Bukkit plugin that illustrates how to use ProtocolLib.
* 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.itemdisguise;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Server;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.FieldAccessException;
public class HideEnchantmentsListener {
private final Server server;
private final Logger logger;
public HideEnchantmentsListener(Server server, Logger logger) {
this.server = server;
this.logger = logger;
}
public void addListener(ProtocolManager protocolManager, JavaPlugin plugin) {
// Hide all enchantments
protocolManager.addPacketListener(new PacketAdapter(plugin, ConnectionSide.SERVER_SIDE, 0x67, 0x68, 0x3E) {
@Override
public void onPacketSending(PacketEvent event) {
PacketContainer packet = event.getPacket();
try {
// Item packets
switch (event.getPacketID()) {
case 0x67: // Set slot
removeEnchantments(packet.getItemModifier().read(0));
break;
case 0x68: // Set Window Items
ItemStack[] elements = packet.getItemArrayModifier().read(0);
for (int i = 0; i < elements.length; i++) {
if (elements[i] != null) {
removeEnchantments(elements[i]);
}
}
break;
case 0x3E: // Sound effect
event.setCancelled(true);
break;
}
} catch (FieldAccessException e) {
logger.log(Level.SEVERE, "Couldn't access field.", e);
}
}
});
// Censor
protocolManager.addPacketListener(new PacketAdapter(plugin, ConnectionSide.CLIENT_SIDE, 0x3) {
@Override
public void onPacketReceiving(PacketEvent event) {
if (event.getPacketID() == 0x3) {
try {
String message = event.getPacket().
getSpecificModifier(String.class).read(0);
if (message.contains("shit") || message.contains("fuck")) {
event.setCancelled(true);
event.getPlayer().sendMessage("Bad manners!");
}
} catch (FieldAccessException e) {
logger.log(Level.SEVERE, "Couldn't access field.", e);
}
}
}
});
}
public Server getServer() {
return server;
}
public void removeListener(ProtocolManager protocolManager, JavaPlugin plugin) {
// Just remove every adapter with this plugin
protocolManager.removePacketListeners(plugin);
}
private void removeEnchantments(ItemStack stack) {
if (stack == null)
return;
Object[] copy = stack.getEnchantments().keySet().toArray();
for (Object enchantment : copy) {
stack.removeEnchantment((Enchantment) enchantment);
}
}
}
@@ -1,99 +1,99 @@
/*
* ItemDisguise - A simple Bukkit plugin that illustrates how to use ProtocolLib.
* 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.itemdisguise;
import java.util.logging.Logger;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketContainer;
public class ItemDisguiseMod extends JavaPlugin {
private ProtocolManager protocolManager;
private Logger logger;
private HideEnchantmentsListener enchantmentsListener;
@Override
public void onEnable() {
logger = getLoggerSafely();
protocolManager = ProtocolLibrary.getProtocolManager();
enchantmentsListener = new HideEnchantmentsListener(getServer(), logger);
enchantmentsListener.addListener(protocolManager, this);
}
@Override
public void onDisable() {
enchantmentsListener.removeListener(protocolManager, this);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof Player) {
Player player = (Player) sender;
if (label.equalsIgnoreCase("explosion")) {
PacketContainer fakeExplosion = protocolManager.createPacket(0x3C);
// Set the coordinates
try {
fakeExplosion.getSpecificModifier(double.class).
write(0, player.getLocation().getX()).
write(1, player.getLocation().getY()).
write(2, player.getLocation().getZ());
fakeExplosion.getSpecificModifier(float.class).
write(0, 3.0F);
protocolManager.sendServerPacket(player, fakeExplosion);
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}
return false;
}
// Get the Bukkit logger first, before we try to create our own
private Logger getLoggerSafely() {
Logger log = null;
try {
log = getLogger();
} catch (Throwable e) {
// We'll handle it
}
if (log == null)
log = Logger.getLogger("Minecraft");
return log;
}
}
/*
* ItemDisguise - A simple Bukkit plugin that illustrates how to use ProtocolLib.
* 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.itemdisguise;
import java.util.logging.Logger;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketContainer;
public class ItemDisguiseMod extends JavaPlugin {
private ProtocolManager protocolManager;
private Logger logger;
private HideEnchantmentsListener enchantmentsListener;
@Override
public void onEnable() {
logger = getLoggerSafely();
protocolManager = ProtocolLibrary.getProtocolManager();
enchantmentsListener = new HideEnchantmentsListener(getServer(), logger);
enchantmentsListener.addListener(protocolManager, this);
}
@Override
public void onDisable() {
enchantmentsListener.removeListener(protocolManager, this);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof Player) {
Player player = (Player) sender;
if (label.equalsIgnoreCase("explosion")) {
PacketContainer fakeExplosion = protocolManager.createPacket(0x3C);
// Set the coordinates
try {
fakeExplosion.getSpecificModifier(double.class).
write(0, player.getLocation().getX()).
write(1, player.getLocation().getY()).
write(2, player.getLocation().getZ());
fakeExplosion.getSpecificModifier(float.class).
write(0, 3.0F);
protocolManager.sendServerPacket(player, fakeExplosion);
} catch (Exception e) {
e.printStackTrace();
}
}
return true;
}
return false;
}
// Get the Bukkit logger first, before we try to create our own
private Logger getLoggerSafely() {
Logger log = null;
try {
log = getLogger();
} catch (Throwable e) {
// We'll handle it
}
if (log == null)
log = Logger.getLogger("Minecraft");
return log;
}
}
@@ -1,14 +1,14 @@
name: ItemDisguiseMod
version: 1.0.0
description: Change the appearance of inventory items.
author: Comphenix
website: http://www.comphenix.net/ItemDisguise
main: com.comphenix.itemdisguise.ItemDisguiseMod
depends: [ProtocolLib]
database: false
commands:
explosion:
description: Creates a fake explosion around the caller.
name: ItemDisguiseMod
version: 1.0.0
description: Change the appearance of inventory items.
author: Comphenix
website: http://www.comphenix.net/ItemDisguise
main: com.comphenix.itemdisguise.ItemDisguiseMod
depends: [ProtocolLib]
database: false
commands:
explosion:
description: Creates a fake explosion around the caller.
usage: /<command>
+339
View File
@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
+27 -14
View File
@@ -1,14 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" output="class_test" path="test"/>
<classpathentry kind="lib" path="D:/Games/Minecraft/Server Mods/Server/craftbukkit-1.3.1-R2.0.jar"/>
<classpathentry kind="lib" path="lib/cglib-nodep-2.2.3.jar">
<attributes>
<attribute name="javadoc_location" value="jar:file:/D:/Kristian/Programmer/Binaries/cglib.2.2.3/cglib-docs-2.2.3.jar!/"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jre6"/>
<classpathentry kind="output" path="class"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry including="**/*.java" kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
+1
View File
@@ -0,0 +1 @@
target/
+23 -17
View File
@@ -1,17 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ProtocolLib</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ProtocolLib</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,4 @@
eclipse.preferences.version=1
encoding//src/main/java=cp1252
encoding//src/test/java=cp1252
encoding/<project>=cp1252
@@ -1,11 +1,12 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.6
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.6
@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
-14
View File
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project default="compile" name="Project ItemDisguise" basedir=".">
<!--ANT 1.7 is required -->
<target name="compile">
<jar destfile="bin/ProtocolLib.jar" filesetmanifest="mergewithoutmain" excludes="test/*">
<manifest>
<attribute name="Main-Class" value="com.comphenix.itemdisguise.Application"/>
<attribute name="Class-Path" value="."/>
</manifest>
<fileset dir="class"/>
<zipfileset excludes="META-INF/*.SF" src="lib/cglib-nodep-2.2.3.jar"/>
</jar>
</target>
</project>
+187
View File
@@ -0,0 +1,187 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<name>ProtocolLib</name>
<version>1.4.0</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>http://dev.bukkit.org/server-mods/protocollib/</url>
<developers>
<developer>
<id>aadnk</id>
<name>Kristian S. Stangeland</name>
<email>kr_stang@hotmail.com</email>
<url>http://comphenix.net/</url>
<roles>
<role>developer</role>
<role>maintainer</role>
</roles>
<timezone>1</timezone>
</developer>
</developers>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
<url>http://www.gnu.org/licenses/gpl-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:git://github.com/aadnk/ProtocolLib.git</connection>
<developerConnection>scm:git:git@github.com:aadnk/ProtocolLib.git</developerConnection>
<url>https://github.com/aadnk/ProtocolLib</url>
</scm>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<defaultGoal>clean install</defaultGoal>
<resources>
<resource>
<directory>src/main/java</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>2.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>true</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>net.sf</pattern>
<shadedPattern>com.comphenix.net.sf</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<excludes>
<exclude>org.bukkit:craftbukkit</exclude>
<exclude>junit:junit</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release-sign-artifacts</id>
<build>
<plugins>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
<repository>
<id>bukkit-rep</id>
<url>http://repo.bukkit.org/content/groups/public</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId>
<version>1.3.2-R1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>hamcrest-core</artifactId>
<groupId>org.hamcrest</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.5.2</version>
<configuration>
<threshold>High</threshold>
<effort>Default</effort>
</configuration>
</plugin>
</plugins>
</reporting>
<distributionManagement>
<repository>
<id>comphenix-releases</id>
<name>Comphenix Maven Releases</name>
<url>http://repo.comphenix.net/content/repositories/releases/</url>
</repository>
<snapshotRepository>
<id>comphenix-snapshots</id>
<name>Comphenix Maven Snapshots</name>
<url>http://repo.comphenix.net/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<properties>
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
</properties>
</project>
Binary file not shown.
+215
View File
@@ -0,0 +1,215 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>1.4.0</version>
<packaging>jar</packaging>
<description>Provides read/write access to the Minecraft protocol.</description>
<properties>
<project.build.sourceEncoding>cp1252</project.build.sourceEncoding>
</properties>
<distributionManagement>
<repository>
<id>comphenix-releases</id>
<name>Comphenix Maven Releases</name>
<url>http://repo.comphenix.net/content/repositories/releases/</url>
</repository>
<snapshotRepository>
<id>comphenix-snapshots</id>
<name>Comphenix Maven Snapshots</name>
<url>http://repo.comphenix.net/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<repositories>
<repository>
<id>bukkit-rep</id>
<url>http://repo.bukkit.org/content/groups/public</url>
</repository>
</repositories>
<build>
<defaultGoal>clean install</defaultGoal>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>src/main/java</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>true</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>net.sf</pattern>
<shadedPattern>com.comphenix.net.sf</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<excludes>
<exclude>org.bukkit:craftbukkit</exclude>
<exclude>junit:junit</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.5.2</version>
<configuration>
<threshold>High</threshold>
<effort>Default</effort>
</configuration>
</plugin>
</plugins>
</reporting>
<profiles>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<url>http://dev.bukkit.org/server-mods/protocollib/</url>
<name>ProtocolLib</name>
<scm>
<connection>scm:git:git://github.com/aadnk/ProtocolLib.git</connection>
<developerConnection>scm:git:git@github.com:aadnk/ProtocolLib.git</developerConnection>
<url>https://github.com/aadnk/ProtocolLib</url>
</scm>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
<url>http://www.gnu.org/licenses/gpl-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>aadnk</id>
<name>Kristian S. Stangeland</name>
<email>kr_stang@hotmail.com</email>
<url>http://comphenix.net/</url>
<roles>
<role>developer</role>
<role>maintainer</role>
</roles>
<timezone>1</timezone>
</developer>
</developers>
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId>
<version>1.3.2-R1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -1,35 +0,0 @@
package com.comphenix.protocol.async;
/**
* A runnable representing a asynchronous event listener.
*
* @author Kristian
*/
public interface AsyncRunnable extends Runnable {
/**
* Retrieve a unique worker ID.
* @return Unique worker ID.
*/
public int getID();
/**
* Stop the given runnable.
* <p>
* This may not occur right away.
* @return TRUE if the thread was stopped, FALSE if it was already stopped.
*/
public boolean stop() throws InterruptedException;
/**
* Determine if we're running or not.
* @return TRUE if we're running, FALSE otherwise.
*/
public boolean isRunning();
/**
* Determine if this runnable has already run its course.
* @return TRUE if it has been stopped, FALSE otherwise.
*/
boolean isFinished();
}
@@ -1,40 +0,0 @@
package com.comphenix.protocol.async;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.base.Preconditions;
import com.google.common.collect.ComparisonChain;
/**
* Provides a comparable to a packet event.
*
* @author Kristian
*/
class PacketEventHolder implements Comparable<PacketEventHolder> {
private PacketEvent event;
/**
* A wrapper that ensures the packet event is ordered by sending index.
* @param event - packet event to wrap.
*/
public PacketEventHolder(PacketEvent event) {
this.event = Preconditions.checkNotNull(event, "Event must be non-null");
}
/**
* Retrieve the stored event.
* @return The stored event.
*/
public PacketEvent getEvent() {
return event;
}
@Override
public int compareTo(PacketEventHolder other) {
AsyncMarker marker = other != null ? other.getEvent().getAsyncMarker() : null;
return ComparisonChain.start().
compare(event.getAsyncMarker(), marker).
result();
}
}
@@ -1,27 +0,0 @@
package com.comphenix.protocol.injector;
import net.minecraft.server.Packet;
import com.comphenix.protocol.events.PacketEvent;
public interface ListenerInvoker {
/**
* Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke.
*/
public abstract void invokePacketRecieving(PacketEvent event);
/**
* Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke.
*/
public abstract void invokePacketSending(PacketEvent event);
/**
* Retrieve the associated ID of a packet.
* @param packet - the packet.
* @return The packet ID.
*/
public abstract int getPacketID(Packet packet);
}
@@ -1,126 +0,0 @@
package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException;
import net.minecraft.server.Packet;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.logging.Logger;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.ListenerInvoker;
/**
* Injection method that overrides the NetworkHandler itself, and it's sendPacket-method.
*
* @author Kristian
*/
class NetworkObjectInjector extends PlayerInjector {
// Determine if we're listening
private Set<Integer> sendingFilters;
public NetworkObjectInjector(Logger logger, Player player, ListenerInvoker invoker, Set<Integer> sendingFilters) throws IllegalAccessException {
super(logger, player, invoker);
this.sendingFilters = sendingFilters;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue();
if (networkDelegate != null) {
try {
// Note that invocation target exception is a wrapper for a checked exception
queueMethod.invoke(networkDelegate, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access queue method.", e);
}
} else {
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
}
}
@Override
public void checkListener(PacketListener listener) {
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
}
}
@Override
public void injectManager() {
if (networkManager != null) {
final Class<?> networkInterface = networkManagerField.getType();
final Object networkDelegate = networkManagerRef.getOldValue();
if (!networkInterface.isInterface()) {
throw new UnsupportedOperationException(
"Must use CraftBukkit 1.3.0 or later to inject into into NetworkMananger.");
}
// Create our proxy object
Object networkProxy = Proxy.newProxyInstance(networkInterface.getClassLoader(),
new Class<?>[] { networkInterface }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// OH OH! The queue method!
if (method.equals(queueMethod)) {
Packet packet = (Packet) args[0];
if (packet != null) {
packet = handlePacketRecieved(packet);
// A NULL packet indicate cancelling
if (packet != null)
args[0] = packet;
else
return null;
}
}
// Delegate to our underlying class
try {
return method.invoke(networkDelegate, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
});
// Inject it, if we can.
networkManagerRef.setValue(networkProxy);
}
}
@Override
public void cleanupAll() {
// Clean up
networkManagerRef.revertValue();
}
@Override
public boolean canInject() {
return true;
}
}
@@ -1,311 +0,0 @@
package com.comphenix.protocol.injector.player;
import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.server.Packet;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.google.common.collect.ImmutableSet;
/**
* Responsible for injecting into a player's sendPacket method.
*
* @author Kristian
*/
public class PlayerInjectionHandler {
// Server connection injection
private InjectedServerConnection serverInjection;
// The last successful player hook
private PlayerInjector lastSuccessfulHook;
// Player injection
private Map<DataInputStream, Player> connectionLookup = new ConcurrentHashMap<DataInputStream, Player>();
private Map<Player, PlayerInjector> playerInjection = new HashMap<Player, PlayerInjector>();
// Player injection type
private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// Error logger
private Logger logger;
// Whether or not we're closing
private boolean hasClosed;
// Used to invoke events
private ListenerInvoker invoker;
// Enabled packet filters
private Set<Integer> sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
// The class loader we're using
private ClassLoader classLoader;
public PlayerInjectionHandler(ClassLoader classLoader, Logger logger, ListenerInvoker invoker, Server server) {
this.classLoader = classLoader;
this.logger = logger;
this.invoker = invoker;
this.serverInjection = new InjectedServerConnection(logger, server);
}
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
public PlayerInjectHooks getPlayerHook() {
return playerHook;
}
/**
* Add an underlying packet handler of the given ID.
* @param packetID - packet ID to register.
*/
public void addPacketHandler(int packetID) {
sendingFilters.add(packetID);
}
/**
* Remove an underlying packet handler of ths ID.
* @param packetID - packet ID to unregister.
*/
public void removePacketHandler(int packetID) {
sendingFilters.remove(packetID);
}
/**
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
public void setPlayerHook(PlayerInjectHooks playerHook) {
this.playerHook = playerHook;
}
/**
* Used to construct a player hook.
* @param player - the player to hook.
* @param hook - the hook type.
* @return A new player hoook
* @throws IllegalAccessException Unable to do our reflection magic.
*/
private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
// Construct the correct player hook
switch (hook) {
case NETWORK_HANDLER_FIELDS:
return new NetworkFieldInjector(classLoader, logger, player, invoker, sendingFilters);
case NETWORK_MANAGER_OBJECT:
return new NetworkObjectInjector(logger, player, invoker, sendingFilters);
case NETWORK_SERVER_OBJECT:
return new NetworkServerInjector(classLoader, logger, player, invoker, sendingFilters, serverInjection);
default:
throw new IllegalArgumentException("Cannot construct a player injector.");
}
}
public Player getPlayerByConnection(DataInputStream inputStream) {
return connectionLookup.get(inputStream);
}
/**
* Initialize a player hook, allowing us to read server packets.
* @param manager - the main packet filter manager.
* @param player - player to hook.
*/
public void injectPlayer(Player player) {
PlayerInjector injector = null;
PlayerInjectHooks currentHook = playerHook;
boolean firstPlayer = lastSuccessfulHook == null;
// Don't inject if the class has closed
if (!hasClosed && player != null && !playerInjection.containsKey(player)) {
while (true) {
try {
injector = getHookInstance(player, currentHook);
injector.initialize();
injector.injectManager();
DataInputStream inputStream = injector.getInputStream(false);
if (!player.isOnline() || inputStream == null) {
throw new PlayerLoggedOutException();
}
playerInjection.put(player, injector);
connectionLookup.put(inputStream, player);
break;
} catch (PlayerLoggedOutException e) {
throw e;
} catch (Exception e) {
// Mark this injection attempt as a failure
logger.log(Level.SEVERE, "Player hook " + currentHook.toString() + " failed.", e);
// Clean up as much as possible
try {
if (injector != null)
injector.cleanupAll();
} catch (Exception e2) {
logger.log(Level.WARNING, "Cleaing up after player hook failed.", e);
}
if (currentHook.ordinal() > 0) {
// Choose the previous player hook type
currentHook = PlayerInjectHooks.values()[currentHook.ordinal() - 1];
logger.log(Level.INFO, "Switching to " + currentHook.toString() + " instead.");
} else {
// UTTER FAILURE
playerInjection.put(player, null);
return;
}
}
}
// Update values
if (injector != null)
lastSuccessfulHook = injector;
if (currentHook != playerHook || firstPlayer)
setPlayerHook(currentHook);
}
}
/**
* Unregisters the given player.
* @param player - player to unregister.
*/
public void uninjectPlayer(Player player) {
if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.get(player);
if (injector != null) {
DataInputStream input = injector.getInputStream(true);
injector.cleanupAll();
playerInjection.remove(player);
connectionLookup.remove(input);
}
}
}
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
getInjector(reciever).sendServerPacket(packet.getHandle(), filters);
}
private PlayerInjector getInjector(Player player) {
if (!playerInjection.containsKey(player)) {
// What? Try to inject again.
injectPlayer(player);
}
PlayerInjector injector = playerInjection.get(player);
// Check that the injector was sucessfully added
if (injector != null)
return injector;
else
throw new IllegalArgumentException("Player has no injected handler.");
}
/**
* Determine if the given listeners are valid.
* @param listeners - listeners to check.
*/
public void checkListener(Set<PacketListener> listeners) {
// Make sure the current listeners are compatible
if (lastSuccessfulHook != null) {
for (PacketListener listener : listeners) {
try {
checkListener(listener);
} catch (IllegalStateException e) {
logger.log(Level.WARNING, "Unsupported listener.", e);
}
}
}
}
/**
* Determine if a listener is valid or not.
* @param listener - listener to check.
* @throws IllegalStateException If the given listener's whitelist cannot be fulfilled.
*/
public void checkListener(PacketListener listener) {
try {
if (lastSuccessfulHook != null)
lastSuccessfulHook.checkListener(listener);
} catch (Exception e) {
throw new IllegalStateException("Registering listener " + PacketAdapter.getPluginName(listener) + " failed", e);
}
}
/**
* Process a packet as if it were sent by the given player.
* @param player - the sender.
* @param mcPacket - the packet to process.
* @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error.
*/
public void processPacket(Player player, Packet mcPacket) throws IllegalAccessException, InvocationTargetException {
PlayerInjector injector = getInjector(player);
injector.processPacket(mcPacket);
}
/**
* Retrieve the current list of registered sending listeners.
* @return List of the sending listeners's packet IDs.
*/
public Set<Integer> getSendingFilters() {
return ImmutableSet.copyOf(sendingFilters);
}
/**
* Retrieve the current logger.
* @return Error logger.
*/
public Logger getLogger() {
return logger;
}
public void close() {
// Guard
if (hasClosed || playerInjection == null)
return;
// Remove everything
for (PlayerInjector injection : playerInjection.values()) {
if (injection != null) {
injection.cleanupAll();
}
}
// Remove server handler
serverInjection.cleanupAll();
hasClosed = true;
playerInjection.clear();
connectionLookup.clear();
invoker = null;
}
}
@@ -1,153 +0,0 @@
package com.comphenix.protocol.injector.player;
import java.util.Collection;
import java.util.List;
import com.google.common.base.Objects;
import com.google.common.collect.BiMap;
import com.google.common.collect.ForwardingList;
import com.google.common.collect.HashBiMap;
/**
* Represents an array list that wraps another list, while automatically replacing one element with another.
* <p>
* The replaced elements can be recovered.
*
* @author Kristian
* @param <TKey> - type of the elements we're replacing.
*/
class ReplacedArrayList<TKey> extends ForwardingList<TKey> {
private BiMap<TKey, TKey> replaceMap = HashBiMap.create();
private List<TKey> underlyingList;
public ReplacedArrayList(List<TKey> underlyingList) {
this.underlyingList = underlyingList;
}
/**
* Invoked when a element inserted is replaced.
* @param inserting - the element inserted.
* @param replacement - the element that it should replace.
*/
protected void onReplacing(TKey inserting, TKey replacement) {
// Default is to do nothing.
}
@Override
public boolean add(TKey element) {
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
return super.add(replacement);
} else {
return super.add(element);
}
}
@Override
public void add(int index, TKey element) {
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
super.add(index, replacement);
} else {
super.add(index, element);
}
}
@Override
public boolean addAll(Collection<? extends TKey> collection) {
int oldSize = size();
for (TKey element : collection)
add(element);
return size() != oldSize;
}
@Override
public boolean addAll(int index, Collection<? extends TKey> elements) {
int oldSize = size();
for (TKey element : elements)
add(index++, element);
return size() != oldSize;
}
@Override
protected List<TKey> delegate() {
return underlyingList;
}
/**
* Add a replace rule.
* <p>
* This automatically replaces every existing element.
* @param target - instance to find.
* @param replacement - instance to replace with.
*/
public synchronized void addMapping(TKey target, TKey replacement) {
replaceMap.put(target, replacement);
// Replace existing elements
replaceAll(target, replacement);
}
/**
* Revert the given mapping.
* @param target - the instance we replaced.
*/
public synchronized void removeMapping(TKey target) {
// Make sure the mapping exist
if (replaceMap.containsKey(target)) {
TKey replacement = replaceMap.get(target);
replaceMap.remove(target);
// Revert existing elements
replaceAll(replacement, target);
}
}
/**
* Replace all instances of the given object.
* @param find - object to find.
* @param replace - object to replace it with.
*/
public synchronized void replaceAll(TKey find, TKey replace) {
for (int i = 0; i < underlyingList.size(); i++) {
if (Objects.equal(underlyingList.get(i), find)) {
onReplacing(find, replace);
underlyingList.set(i, replace);
}
}
}
/**
* Undo all replacements.
*/
public synchronized void revertAll() {
// No need to do anything else
if (replaceMap.size() < 1)
return;
BiMap<TKey, TKey> inverse = replaceMap.inverse();
for (int i = 0; i < underlyingList.size(); i++) {
TKey replaced = underlyingList.get(i);
if (inverse.containsKey(replaced)) {
TKey original = inverse.get(replaced);
onReplacing(replaced, original);
underlyingList.set(i, original);
}
}
replaceMap.clear();
}
@Override
protected void finalize() throws Throwable {
revertAll();
super.finalize();
}
}
@@ -1,17 +0,0 @@
package com.comphenix.protocol.reflect.compiler;
import com.comphenix.protocol.reflect.StructureModifier;
/**
* Used to save the result of an compilation.
*
* @author Kristian
* @param <TKey> - type of the structure modifier field.
*/
public interface CompileListener<TKey> {
/**
* Invoked when a structure modifier has been successfully compiled.
* @param compiledModifier - the compiled structure modifier.
*/
public void onCompiled(StructureModifier<TKey> compiledModifier);
}
@@ -1,18 +0,0 @@
package com.comphenix.protocol.reflect.instances;
import javax.annotation.Nullable;
/**
* Represents a type generator for specific types.
*
* @author Kristian
*/
public interface InstanceProvider {
/**
* Create an instance given a type, if possible.
* @param type - type to create.
* @return The instance, or NULL if the type cannot be created.
*/
public abstract Object create(@Nullable Class<?> type);
}
@@ -1,25 +1,30 @@
/*
* 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;
public class Application {
public static void main(String[] args) {
// For now, though we might consider making a proper application
System.out.println("This is a Bukkit library. Place it in the plugin-folder and restart the server!");
}
}
/*
* 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;
/**
* Ignore this class.
*
* @author Kristian
*/
public class Application {
public static void main(String[] args) {
// For now, though we might consider making a proper application
System.out.println("This is a Bukkit library. Place it in the plugin-folder and restart the server!");
}
}
@@ -1,77 +1,102 @@
package com.comphenix.protocol;
import java.util.Set;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.async.AsyncListenerHandler;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
/**
* Represents a asynchronous packet handler.
*
* @author Kristian
*/
public interface AsynchronousManager {
/**
* Registers an asynchronous packet handler.
* <p>
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
* @param listener - the packet listener that will recieve these asynchronous events.
* @return An asynchrouns handler.
*/
public abstract AsyncListenerHandler registerAsyncHandler(PacketListener listener);
/**
* Unregisters and closes the given asynchronous handler.
* @param handler - asynchronous handler.
*/
public abstract void unregisterAsyncHandler(AsyncListenerHandler handler);
/**
* Unregisters every asynchronous handler associated with this plugin.
* @param plugin - the original plugin.
*/
public void unregisterAsyncHandlers(Plugin plugin);
/**
* Retrieves a immutable set containing the ID of the sent server packets that will be
* observed by the asynchronous listeners.
* @return Every filtered server packet.
*/
public abstract Set<Integer> getSendingFilters();
/**
* Retrieves a immutable set containing the ID of the recieved client packets that will be
* observed by the asynchronous listeners.
* @return Every filtered client packet.
*/
public abstract Set<Integer> getReceivingFilters();
/**
* Determine if a given synchronous packet has asynchronous listeners.
* @param packet - packet to test.
* @return TRUE if it does, FALSE otherwise.
*/
public abstract boolean hasAsynchronousListeners(PacketEvent packet);
/**
* Retrieve the default packet stream.
* @return Default packet stream.
*/
public abstract PacketStream getPacketStream();
/**
* Retrieve the default error logger.
* @return Default logger.
*/
public abstract Logger getLogger();
/**
* Remove listeners, close threads and transmit every delayed packet.
*/
public abstract void cleanupAll();
/*
* 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.Set;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.async.AsyncListenerHandler;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
/**
* Represents a asynchronous packet handler.
*
* @author Kristian
*/
public interface AsynchronousManager {
/**
* Registers an asynchronous packet handler.
* <p>
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
* @param listener - the packet listener that will recieve these asynchronous events.
* @return An asynchrouns handler.
*/
public abstract AsyncListenerHandler registerAsyncHandler(PacketListener listener);
/**
* Unregisters and closes the given asynchronous handler.
* @param handler - asynchronous handler.
*/
public abstract void unregisterAsyncHandler(AsyncListenerHandler handler);
/**
* Unregisters every asynchronous handler associated with this plugin.
* @param plugin - the original plugin.
*/
public void unregisterAsyncHandlers(Plugin plugin);
/**
* Retrieves a immutable set containing the ID of the sent server packets that will be
* observed by the asynchronous listeners.
* @return Every filtered server packet.
*/
public abstract Set<Integer> getSendingFilters();
/**
* Retrieves a immutable set containing the ID of the recieved client packets that will be
* observed by the asynchronous listeners.
* @return Every filtered client packet.
*/
public abstract Set<Integer> getReceivingFilters();
/**
* Determine if a given synchronous packet has asynchronous listeners.
* @param packet - packet to test.
* @return TRUE if it does, FALSE otherwise.
*/
public abstract boolean hasAsynchronousListeners(PacketEvent packet);
/**
* Retrieve the default packet stream.
* @return Default packet stream.
*/
public abstract PacketStream getPacketStream();
/**
* Retrieve the default error logger.
* @return Default logger.
*/
public abstract Logger getLogger();
/**
* Remove listeners, close threads and transmit every delayed packet.
*/
public abstract void cleanupAll();
/**
* Signal that a packet is ready to be transmitted.
* <p>
* This should only be called if {@link com.comphenix.protocol.async.AsyncMarker#incrementProcessingDelay() AsyncMarker.incrementProcessingDelay()}
* has been called previously.
* @param packet - packet to signal.
*/
public abstract void signalPacketTransmission(PacketEvent packet);
}
@@ -1,55 +1,72 @@
package com.comphenix.protocol;
import java.lang.reflect.InvocationTargetException;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer;
/**
* Represents a object capable of sending or receiving packets.
*
* @author Kristian
*/
public interface PacketStream {
/**
* Send a packet to the given player.
* @param reciever - the reciever.
* @param packet - packet to send.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player reciever, PacketContainer packet)
throws InvocationTargetException;
/**
* Send a packet to the given player.
* @param reciever - the reciever.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
throws InvocationTargetException;
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet)
throws IllegalAccessException, InvocationTargetException;
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param filters - whether or not to invoke any packet filters.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
throws IllegalAccessException, InvocationTargetException;
}
/*
* 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;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer;
/**
* Represents a object capable of sending or receiving packets.
*
* @author Kristian
*/
public interface PacketStream {
/**
* Send a packet to the given player.
* @param reciever - the reciever.
* @param packet - packet to send.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player reciever, PacketContainer packet)
throws InvocationTargetException;
/**
* Send a packet to the given player.
* @param reciever - the reciever.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
throws InvocationTargetException;
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet)
throws IllegalAccessException, InvocationTargetException;
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param filters - whether or not to invoke any packet filters.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
throws IllegalAccessException, InvocationTargetException;
}
@@ -1,198 +1,257 @@
package com.comphenix.protocol;
import com.comphenix.protocol.reflect.IntEnum;
/**
* List of known packet IDs since 1.3.2.
*
* @author Kristian
*/
public final class Packets {
/**
* List of packets sent only by the server.
* @author Kristian
*/
public static final class Server extends IntEnum {
/**
* The singleton instance. Can also be retrieved from the parent class.
*/
private static Server INSTANCE = new Server();
public static final int KEEP_ALIVE = 0;
public static final int LOGIN = 1;
public static final int CHAT = 3;
public static final int UPDATE_TIME = 4;
public static final int ENTITY_EQUIPMENT = 5;
public static final int SPAWN_POSITION = 6;
public static final int UPDATE_HEALTH = 8;
public static final int RESPAWN = 9;
public static final int FLYING = 10;
public static final int PLAYER_POSITION = 11;
public static final int PLAYER_LOOK = 12;
public static final int PLAYER_LOOK_MOVE = 13;
public static final int ENTITY_LOCATION_ACTION = 17;
public static final int ARM_ANIMATION = 18;
public static final int NAMED_ENTITY_SPAWN = 20;
public static final int PICKUP_SPAWN = 21;
public static final int COLLECT = 22;
public static final int VEHICLE_SPAWN = 23;
public static final int MOB_SPAWN = 24;
public static final int ENTITY_PAINTING = 25;
public static final int ADD_EXP_ORB = 26;
public static final int ENTITY_VELOCITY = 28;
public static final int DESTROY_ENTITY = 29;
public static final int ENTITY = 30;
public static final int REL_ENTITY_MOVE = 31;
public static final int ENTITY_LOOK = 32;
public static final int REL_ENTITY_MOVE_LOOK = 33;
public static final int ENTITY_TELEPORT = 34;
public static final int ENTITY_HEAD_ROTATION = 35;
public static final int ENTITY_STATUS = 38;
public static final int ATTACH_ENTITY = 39;
public static final int ENTITY_METADATA = 40;
public static final int MOB_EFFECT = 41;
public static final int REMOVE_MOB_EFFECT = 42;
public static final int SET_EXPERIENCE = 43;
public static final int MAP_CHUNK = 51;
public static final int MULTI_BLOCK_CHANGE = 52;
public static final int BLOCK_CHANGE = 53;
public static final int PLAY_NOTE_BLOCK = 54;
public static final int BLOCK_BREAK_ANIMATION = 55;
public static final int MAP_CHUNK_BULK = 56;
public static final int EXPLOSION = 60;
public static final int WORLD_EVENT = 61;
public static final int NAMED_SOUND_EFFECT = 62;
public static final int BED = 70;
public static final int WEATHER = 71;
public static final int OPEN_WINDOW = 100;
public static final int CLOSE_WINDOW = 101;
public static final int SET_SLOT = 103;
public static final int WINDOW_ITEMS = 104;
public static final int CRAFT_PROGRESS_BAR = 105;
public static final int TRANSACTION = 106;
public static final int SET_CREATIVE_SLOT = 107;
public static final int UPDATE_SIGN = 130;
public static final int ITEM_DATA = 131;
public static final int TILE_ENTITY_DATA = 132;
public static final int STATISTIC = 200;
public static final int PLAYER_INFO = 201;
public static final int ABILITIES = 202;
public static final int TAB_COMPLETE = 203;
public static final int CUSTOM_PAYLOAD = 250;
public static final int KEY_RESPONSE = 252;
public static final int KEY_REQUEST = 253;
public static final int KICK_DISCONNECT = 255;
/**
* A registry that parses between names and packet IDs.
* @return The current server registry.
*/
public static Server getRegistry() {
return INSTANCE;
}
// We only allow a single instance of this class
private Server() {
super();
}
}
/**
* List of packets sent by the client.
* @author Kristian
*/
public static class Client extends IntEnum {
/**
* The singleton instance. Can also be retrieved from the parent class.
*/
private static Client INSTANCE = new Client();
public static final int KEEP_ALIVE = 0;
public static final int LOGIN = 1;
public static final int HANDSHAKE = 2;
public static final int CHAT = 3;
public static final int USE_ENTITY = 7;
public static final int RESPAWN = 9;
public static final int FLYING = 10;
public static final int PLAYER_POSITION = 11;
public static final int PLAYER_LOOK = 12;
public static final int PLAYER_LOOK_MOVE = 13;
public static final int BLOCK_DIG = 14;
public static final int PLACE = 15;
public static final int BLOCK_ITEM_SWITCH = 16;
public static final int ARM_ANIMATION = 18;
public static final int ENTITY_ACTION = 19;
public static final int CLOSE_WINDOW = 101;
public static final int WINDOW_CLICK = 102;
public static final int TRANSACTION = 106;
public static final int SET_CREATIVE_SLOT = 107;
public static final int BUTTON_CLICK = 108;
public static final int UPDATE_SIGN = 130;
public static final int ABILITIES = 202;
public static final int TAB_COMPLETE = 203;
public static final int LOCALE_AND_VIEW_DISTANCE = 204;
public static final int CLIENT_COMMAND = 205;
public static final int CUSTOM_PAYLOAD = 250;
public static final int KEY_RESPONSE = 252;
public static final int GET_INFO = 254;
public static final int KICK_DISCONNECT = 255;
/**
* A registry that parses between names and packet IDs.
* @return The current client registry.
*/
public static Client getRegistry() {
return INSTANCE;
}
// Like above
private Client() {
super();
}
}
/**
* A registry that parses between names and packet IDs.
* @return The current client registry.
*/
public static Server getServerRegistry() {
return Server.getRegistry();
}
/**
* A registry that parses between names and packet IDs.
* @return The current server registry.
*/
public static Client getClientRegistry() {
return Client.INSTANCE;
}
/**
* Find a packet by name. Must be capitalized and use underscores.
* @param name - name of packet to find.
* @return The packet ID found.
*/
public static int valueOf(String name) {
Integer serverAttempt = Server.INSTANCE.valueOf(name);
if (serverAttempt != null)
return serverAttempt;
else
return Client.INSTANCE.valueOf(name);
}
/**
* Retrieves the name of a packet.
* @param packetID - packet to retrieve name.
* @return The name, or NULL if unable to find such a packet.
*/
public static String getDeclaredName(int packetID) {
String serverAttempt = Server.INSTANCE.getDeclaredName(packetID);
if (serverAttempt != null)
return serverAttempt;
else
return Client.INSTANCE.getDeclaredName(packetID);
}
}
/*
* 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.Set;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.IntEnum;
/**
* List of known packet IDs since 1.3.2.
*
* @author Kristian
*/
public final class Packets {
/**
* List of packets sent only by the server.
* @author Kristian
*/
public static final class Server extends IntEnum {
/**
* The singleton instance. Can also be retrieved from the parent class.
*/
private static Server INSTANCE = new Server();
public static final int KEEP_ALIVE = 0;
public static final int LOGIN = 1;
public static final int CHAT = 3;
public static final int UPDATE_TIME = 4;
public static final int ENTITY_EQUIPMENT = 5;
public static final int SPAWN_POSITION = 6;
public static final int UPDATE_HEALTH = 8;
public static final int RESPAWN = 9;
public static final int FLYING = 10;
public static final int PLAYER_POSITION = 11;
public static final int PLAYER_LOOK = 12;
public static final int PLAYER_LOOK_MOVE = 13;
public static final int ENTITY_LOCATION_ACTION = 17;
public static final int ARM_ANIMATION = 18;
public static final int NAMED_ENTITY_SPAWN = 20;
public static final int PICKUP_SPAWN = 21;
public static final int COLLECT = 22;
public static final int VEHICLE_SPAWN = 23;
public static final int MOB_SPAWN = 24;
public static final int ENTITY_PAINTING = 25;
public static final int ADD_EXP_ORB = 26;
public static final int ENTITY_VELOCITY = 28;
public static final int DESTROY_ENTITY = 29;
public static final int ENTITY = 30;
public static final int REL_ENTITY_MOVE = 31;
public static final int ENTITY_LOOK = 32;
public static final int REL_ENTITY_MOVE_LOOK = 33;
public static final int ENTITY_TELEPORT = 34;
public static final int ENTITY_HEAD_ROTATION = 35;
public static final int ENTITY_STATUS = 38;
public static final int ATTACH_ENTITY = 39;
public static final int ENTITY_METADATA = 40;
public static final int MOB_EFFECT = 41;
public static final int REMOVE_MOB_EFFECT = 42;
public static final int SET_EXPERIENCE = 43;
public static final int MAP_CHUNK = 51;
public static final int MULTI_BLOCK_CHANGE = 52;
public static final int BLOCK_CHANGE = 53;
public static final int PLAY_NOTE_BLOCK = 54;
public static final int BLOCK_BREAK_ANIMATION = 55;
public static final int MAP_CHUNK_BULK = 56;
public static final int EXPLOSION = 60;
public static final int WORLD_EVENT = 61;
public static final int NAMED_SOUND_EFFECT = 62;
public static final int BED = 70;
public static final int WEATHER = 71;
public static final int OPEN_WINDOW = 100;
public static final int CLOSE_WINDOW = 101;
public static final int SET_SLOT = 103;
public static final int WINDOW_ITEMS = 104;
public static final int CRAFT_PROGRESS_BAR = 105;
public static final int TRANSACTION = 106;
public static final int SET_CREATIVE_SLOT = 107;
public static final int UPDATE_SIGN = 130;
public static final int ITEM_DATA = 131;
public static final int TILE_ENTITY_DATA = 132;
public static final int STATISTIC = 200;
public static final int PLAYER_INFO = 201;
public static final int ABILITIES = 202;
public static final int TAB_COMPLETE = 203;
public static final int CUSTOM_PAYLOAD = 250;
public static final int KEY_RESPONSE = 252;
public static final int KEY_REQUEST = 253;
public static final int KICK_DISCONNECT = 255;
/**
* A registry that parses between names and packet IDs.
* @return The current server registry.
*/
public static Server getRegistry() {
return INSTANCE;
}
/**
* Determine if the given packet is a valid server packet in the current version of Minecraft.
* @param packetID - the packet to test.
* @return TRUE if this packet is supported, FALSE otherwise.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
public static boolean isSupported(int packetID) throws FieldAccessException {
return PacketFilterManager.getServerPackets().contains(packetID);
}
/**
* Retrieve every client packet the current version of Minecraft is aware of.
* @return Every supported server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
public static Set<Integer> getSupported() throws FieldAccessException {
return PacketFilterManager.getServerPackets();
}
// We only allow a single instance of this class
private Server() {
super();
}
}
/**
* List of packets sent by the client.
* @author Kristian
*/
public static class Client extends IntEnum {
/**
* The singleton instance. Can also be retrieved from the parent class.
*/
private static Client INSTANCE = new Client();
public static final int KEEP_ALIVE = 0;
public static final int LOGIN = 1;
public static final int HANDSHAKE = 2;
public static final int CHAT = 3;
public static final int USE_ENTITY = 7;
public static final int RESPAWN = 9;
public static final int FLYING = 10;
public static final int PLAYER_POSITION = 11;
public static final int PLAYER_LOOK = 12;
public static final int PLAYER_LOOK_MOVE = 13;
public static final int BLOCK_DIG = 14;
public static final int PLACE = 15;
public static final int BLOCK_ITEM_SWITCH = 16;
public static final int ARM_ANIMATION = 18;
public static final int ENTITY_ACTION = 19;
public static final int CLOSE_WINDOW = 101;
public static final int WINDOW_CLICK = 102;
public static final int TRANSACTION = 106;
public static final int SET_CREATIVE_SLOT = 107;
public static final int BUTTON_CLICK = 108;
public static final int UPDATE_SIGN = 130;
public static final int ABILITIES = 202;
public static final int TAB_COMPLETE = 203;
public static final int LOCALE_AND_VIEW_DISTANCE = 204;
public static final int CLIENT_COMMAND = 205;
public static final int CUSTOM_PAYLOAD = 250;
public static final int KEY_RESPONSE = 252;
public static final int GET_INFO = 254;
public static final int KICK_DISCONNECT = 255;
/**
* A registry that parses between names and packet IDs.
* @return The current client registry.
*/
public static Client getRegistry() {
return INSTANCE;
}
/**
* Determine if the given packet is a valid client packet in the current version of Minecraft.
* @param packetID - the packet to test.
* @return TRUE if this packet is supported, FALSE otherwise.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
public static boolean isSupported(int packetID) throws FieldAccessException {
return PacketFilterManager.getClientPackets().contains(packetID);
}
/**
* Retrieve every client packet the current version of Minecraft is aware of.
* @return Every supported client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
public static Set<Integer> getSupported() throws FieldAccessException {
return PacketFilterManager.getClientPackets();
}
// Like above
private Client() {
super();
}
}
/**
* A registry that parses between names and packet IDs.
* @return The current client registry.
*/
public static Server getServerRegistry() {
return Server.getRegistry();
}
/**
* A registry that parses between names and packet IDs.
* @return The current server registry.
*/
public static Client getClientRegistry() {
return Client.INSTANCE;
}
/**
* Find a packet by name. Must be capitalized and use underscores.
* @param name - name of packet to find.
* @return The packet ID found.
*/
public static int valueOf(String name) {
Integer serverAttempt = Server.INSTANCE.valueOf(name);
if (serverAttempt != null)
return serverAttempt;
else
return Client.INSTANCE.valueOf(name);
}
/**
* Retrieves the name of a packet.
* @param packetID - packet to retrieve name.
* @return The name, or NULL if unable to find such a packet.
*/
public static String getDeclaredName(int packetID) {
String serverAttempt = Server.INSTANCE.getDeclaredName(packetID);
if (serverAttempt != null)
return serverAttempt;
else
return Client.INSTANCE.getDeclaredName(packetID);
}
}
@@ -1,180 +1,228 @@
/*
* 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.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Server;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
public class ProtocolLibrary extends JavaPlugin {
// There should only be one protocol manager, so we'll make it static
private static PacketFilterManager protocolManager;
// Error logger
private Logger logger;
// Metrics and statistisc
private Statistics statistisc;
// Structure compiler
private BackgroundCompiler backgroundCompiler;
// Used to clean up server packets that have expired.
// But mostly required to simulate recieving client packets.
private int asyncPacketTask = -1;
private int tickCounter = 0;
private static final int ASYNC_PACKET_DELAY = 1;
@Override
public void onLoad() {
logger = getLoggerSafely();
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), logger);
}
@Override
public void onEnable() {
Server server = getServer();
PluginManager manager = server.getPluginManager();
// Initialize background compiler
if (backgroundCompiler == null) {
backgroundCompiler = new BackgroundCompiler(getClassLoader());
BackgroundCompiler.setInstance(backgroundCompiler);
}
// Notify server managers of incompatible plugins
checkForIncompatibility(manager);
// Player login and logout events
protocolManager.registerEvents(manager, this);
// Inject our hook into already existing players
protocolManager.initializePlayers(server.getOnlinePlayers());
// Timeout
createAsyncTask(server);
// Try to enable statistics
try {
statistisc = new Statistics(this);
} catch (IOException e) {
logger.log(Level.SEVERE, "Unable to enable metrics.", e);
} catch (Throwable e) {
logger.log(Level.SEVERE, "Metrics cannot be enabled. Incompatible Bukkit version.", e);
}
}
private void createAsyncTask(Server server) {
try {
if (asyncPacketTask >= 0)
throw new IllegalStateException("Async task has already been created");
// Attempt to create task
asyncPacketTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
@Override
public void run() {
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
// We KNOW we're on the main thread at the moment
manager.sendProcessedPackets(tickCounter++, true);
}
}, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY);
} catch (Throwable e) {
if (asyncPacketTask == -1) {
logger.log(Level.SEVERE, "Unable to create packet timeout task.", e);
}
}
}
private void checkForIncompatibility(PluginManager manager) {
// Plugin authors: Notify me to remove these
String[] incompatiblePlugins = {};
for (String plugin : incompatiblePlugins) {
if (manager.getPlugin(plugin) != null) {
// Check for versions, ect.
logger.severe("Detected incompatible plugin: " + plugin);
}
}
}
@Override
public void onDisable() {
// Disable compiler
if (backgroundCompiler != null) {
backgroundCompiler.shutdownAll();
backgroundCompiler = null;
BackgroundCompiler.setInstance(null);
}
// Clean up
if (asyncPacketTask >= 0) {
getServer().getScheduler().cancelTask(asyncPacketTask);
asyncPacketTask = -1;
}
protocolManager.close();
protocolManager = null;
statistisc = null;
}
/**
* Retrieves the packet protocol manager.
* @return Packet protocol manager, or NULL if it has been disabled.
*/
public static ProtocolManager getProtocolManager() {
return protocolManager;
}
/**
* Retrieve the metrics instance used to measure users of this library.
* <p>
* Note that this method may return NULL when the server is reloading or shutting down.
* @return Metrics instance container.
*/
public Statistics getStatistics() {
return statistisc;
}
// Get the Bukkit logger first, before we try to create our own
private Logger getLoggerSafely() {
Logger log = null;
try {
log = getLogger();
} catch (Throwable e) {
// We'll handle it
}
if (log == null)
log = Logger.getLogger("Minecraft");
return log;
}
}
/*
* 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.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Server;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.MonitorAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.DelayedSingleTask;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
/**
* The main entry point for ProtocolLib.
*
* @author Kristian
*/
public class ProtocolLibrary extends JavaPlugin {
// There should only be one protocol manager, so we'll make it static
private static PacketFilterManager protocolManager;
// Error logger
private Logger logger;
// Metrics and statistisc
private Statistics statistisc;
// Structure compiler
private BackgroundCompiler backgroundCompiler;
// Used to clean up server packets that have expired.
// But mostly required to simulate recieving client packets.
private int asyncPacketTask = -1;
private int tickCounter = 0;
private static final int ASYNC_PACKET_DELAY = 1;
// Used to unhook players after a delay
private DelayedSingleTask unhookTask;
// Used for debugging
private boolean debugListener;
@Override
public void onLoad() {
logger = getLoggerSafely();
unhookTask = new DelayedSingleTask(this);
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, logger);
}
@Override
public void onEnable() {
Server server = getServer();
PluginManager manager = server.getPluginManager();
// Initialize background compiler
if (backgroundCompiler == null) {
backgroundCompiler = new BackgroundCompiler(getClassLoader());
BackgroundCompiler.setInstance(backgroundCompiler);
}
// Notify server managers of incompatible plugins
checkForIncompatibility(manager);
// Player login and logout events
protocolManager.registerEvents(manager, this);
// Worker that ensures that async packets are eventually sent
createAsyncTask(server);
//toggleDebugListener();
// Try to enable statistics
try {
statistisc = new Statistics(this);
} catch (IOException e) {
logger.log(Level.SEVERE, "Unable to enable metrics.", e);
} catch (Throwable e) {
logger.log(Level.SEVERE, "Metrics cannot be enabled. Incompatible Bukkit version.", e);
}
}
/**
* Toggle a listener that prints every sent and received packet.
*/
void toggleDebugListener() {
if (debugListener) {
protocolManager.removePacketListeners(this);
} else {
// DEBUG DEBUG
protocolManager.addPacketListener(new MonitorAdapter(this, ConnectionSide.BOTH, logger) {
@Override
public void onPacketReceiving(PacketEvent event) {
Object handle = event.getPacket().getHandle();
System.out.println(String.format(
"RECEIVING %s@%s from %s.",
handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName()
));
};
@Override
public void onPacketSending(PacketEvent event) {
Object handle = event.getPacket().getHandle();
System.out.println(String.format(
"SENDING %s@%s from %s.",
handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName()
));
}
});
}
debugListener = !debugListener;
}
private void createAsyncTask(Server server) {
try {
if (asyncPacketTask >= 0)
throw new IllegalStateException("Async task has already been created");
// Attempt to create task
asyncPacketTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
@Override
public void run() {
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
// We KNOW we're on the main thread at the moment
manager.sendProcessedPackets(tickCounter++, true);
}
}, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY);
} catch (Throwable e) {
if (asyncPacketTask == -1) {
logger.log(Level.SEVERE, "Unable to create packet timeout task.", e);
}
}
}
private void checkForIncompatibility(PluginManager manager) {
// Plugin authors: Notify me to remove these
String[] incompatiblePlugins = {};
for (String plugin : incompatiblePlugins) {
if (manager.getPlugin(plugin) != null) {
// Check for versions, ect.
logger.severe("Detected incompatible plugin: " + plugin);
}
}
}
@Override
public void onDisable() {
// Disable compiler
if (backgroundCompiler != null) {
backgroundCompiler.shutdownAll();
backgroundCompiler = null;
BackgroundCompiler.setInstance(null);
}
// Clean up
if (asyncPacketTask >= 0) {
getServer().getScheduler().cancelTask(asyncPacketTask);
asyncPacketTask = -1;
}
unhookTask.close();
protocolManager.close();
protocolManager = null;
statistisc = null;
}
/**
* Retrieves the packet protocol manager.
* @return Packet protocol manager, or NULL if it has been disabled.
*/
public static ProtocolManager getProtocolManager() {
return protocolManager;
}
/**
* Retrieve the metrics instance used to measure users of this library.
* <p>
* Note that this method may return NULL when the server is reloading or shutting down.
* @return Metrics instance container.
*/
public Statistics getStatistics() {
return statistisc;
}
// Get the Bukkit logger first, before we try to create our own
private Logger getLoggerSafely() {
Logger log = null;
try {
log = getLogger();
} catch (Throwable e) {
// We'll handle it
}
if (log == null)
log = Logger.getLogger("Minecraft");
return log;
}
}
@@ -1,131 +1,131 @@
/*
* 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.List;
import java.util.Set;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.collect.ImmutableSet;
/**
* Represents an API for accessing the Minecraft protocol.
* @author Kristian
*/
public interface ProtocolManager extends PacketStream {
/**
* Retrieves a list of every registered packet listener.
* @return Every registered packet listener.
*/
public ImmutableSet<PacketListener> getPacketListeners();
/**
* Adds a packet listener.
* <p>
* Adding an already registered listener has no effect. If you need to change the packets
* the current listener is observing, you must first remove the packet listener before you
* can register it again.
* @param listener - new packet listener.
*/
public void addPacketListener(PacketListener listener);
/**
* Removes a given packet listener.
* <p>
* Attempting to remove a listener that doesn't exist has no effect.
* @param listener - the packet listener to remove.
*/
public void removePacketListener(PacketListener listener);
/**
* Removes every listener associated with the given plugin.
* @param plugin - the plugin to unload.
*/
public void removePacketListeners(Plugin plugin);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* @param id - packet ID.
* @return New encapsulated Minecraft packet.
*/
public PacketContainer createPacket(int id);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* <p>
* If set to true, the <i>forceDefaults</i> option will force the system to automatically
* give non-primitive fields in the packet sensible default values. For instance, certain
* packets - like Packet60Explosion - require a List or Set to be non-null. If the
* forceDefaults option is true, the List or Set will be automatically created.
*
* @param id - packet ID.
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
* @return New encapsulated Minecraft packet.
*/
public PacketContainer createPacket(int id, boolean forceDefaults);
/**
* Construct a packet using the special builtin Minecraft constructors.
* @param id - the packet ID.
* @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor.
*/
public PacketConstructor createPacketConstructor(int id, Object... arguments);
/**
* Completely refresh all clients about an entity.
* <p>
* Note that this method is NOT thread safe. If you call this method from anything
* but the main thread, it will throw an exception.
* @param entity - entity to refresh.
* @param observers - the clients to update.
*/
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
/**
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
* @return Every filtered server packet.
*/
public Set<Integer> getSendingFilters();
/**
* Retrieves a immutable set containing the ID of the recieved client packets that will be observed by listeners.
* @return Every filtered client packet.
*/
public Set<Integer> getReceivingFilters();
/**
* Determines whether or not this protocol mananger has been disabled.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isClosed();
/**
* Retrieve the current asyncronous packet manager.
* @return Asyncronous packet manager.
*/
public AsynchronousManager getAsynchronousManager();
/*
* 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.List;
import java.util.Set;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.collect.ImmutableSet;
/**
* Represents an API for accessing the Minecraft protocol.
* @author Kristian
*/
public interface ProtocolManager extends PacketStream {
/**
* Retrieves a list of every registered packet listener.
* @return Every registered packet listener.
*/
public ImmutableSet<PacketListener> getPacketListeners();
/**
* Adds a packet listener.
* <p>
* Adding an already registered listener has no effect. If you need to change the packets
* the current listener is observing, you must first remove the packet listener before you
* can register it again.
* @param listener - new packet listener.
*/
public void addPacketListener(PacketListener listener);
/**
* Removes a given packet listener.
* <p>
* Attempting to remove a listener that doesn't exist has no effect.
* @param listener - the packet listener to remove.
*/
public void removePacketListener(PacketListener listener);
/**
* Removes every listener associated with the given plugin.
* @param plugin - the plugin to unload.
*/
public void removePacketListeners(Plugin plugin);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* @param id - packet ID.
* @return New encapsulated Minecraft packet.
*/
public PacketContainer createPacket(int id);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* <p>
* If set to true, the <i>forceDefaults</i> option will force the system to automatically
* give non-primitive fields in the packet sensible default values. For instance, certain
* packets - like Packet60Explosion - require a List or Set to be non-null. If the
* forceDefaults option is true, the List or Set will be automatically created.
*
* @param id - packet ID.
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
* @return New encapsulated Minecraft packet.
*/
public PacketContainer createPacket(int id, boolean forceDefaults);
/**
* Construct a packet using the special builtin Minecraft constructors.
* @param id - the packet ID.
* @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor.
*/
public PacketConstructor createPacketConstructor(int id, Object... arguments);
/**
* Completely refresh all clients about an entity.
* <p>
* Note that this method is NOT thread safe. If you call this method from anything
* but the main thread, it will throw an exception.
* @param entity - entity to refresh.
* @param observers - the clients to update.
*/
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
/**
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
* @return Every filtered server packet.
*/
public Set<Integer> getSendingFilters();
/**
* Retrieves a immutable set containing the ID of the recieved client packets that will be observed by listeners.
* @return Every filtered client packet.
*/
public Set<Integer> getReceivingFilters();
/**
* Determines whether or not this protocol mananger has been disabled.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isClosed();
/**
* Retrieve the current asyncronous packet manager.
* @return Asyncronous packet manager.
*/
public AsynchronousManager getAsynchronousManager();
}
@@ -1,305 +1,344 @@
package com.comphenix.protocol.async;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.base.Objects;
/**
* Represents a filter manager for asynchronous packets.
*
* @author Kristian
*/
public class AsyncFilterManager implements AsynchronousManager {
private PacketProcessingQueue serverProcessingQueue;
private PacketSendingQueue serverQueue;
private PacketProcessingQueue clientProcessingQueue;
private PacketSendingQueue clientQueue;
private Logger logger;
// The likely main thread
private Thread mainThread;
// Default scheduler
private BukkitScheduler scheduler;
// Our protocol manager
private ProtocolManager manager;
// Current packet index
private AtomicInteger currentSendingIndex = new AtomicInteger();
// Whether or not we're currently cleaning up
private volatile boolean cleaningUp;
public AsyncFilterManager(Logger logger, BukkitScheduler scheduler, ProtocolManager manager) {
// Server packets are synchronized already
this.serverQueue = new PacketSendingQueue(false);
// Client packets must be synchronized
this.clientQueue = new PacketSendingQueue(true);
this.serverProcessingQueue = new PacketProcessingQueue(serverQueue);
this.clientProcessingQueue = new PacketProcessingQueue(clientQueue);
this.scheduler = scheduler;
this.manager = manager;
this.logger = logger;
this.mainThread = Thread.currentThread();
}
@Override
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
return registerAsyncHandler(listener, true);
}
/**
* Registers an asynchronous packet handler.
* <p>
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
* <p>
* Asynchronous events will only be executed if a synchronous listener with the same packets is registered.
* If you already have a synchronous event, call this method with autoInject set to FALSE.
*
* @param listener - the packet listener that will recieve these asynchronous events.
* @param autoInject - whether or not to automatically create the corresponding synchronous listener,
* @return An asynchrouns handler.
*/
public AsyncListenerHandler registerAsyncHandler(PacketListener listener, boolean autoInject) {
AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener);
ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist();
ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist();
// Add listener to either or both processing queue
if (hasValidWhitelist(sendingWhitelist)) {
PacketFilterManager.verifyWhitelist(listener, sendingWhitelist);
serverProcessingQueue.addListener(handler, sendingWhitelist);
}
if (hasValidWhitelist(receivingWhitelist)) {
PacketFilterManager.verifyWhitelist(listener, receivingWhitelist);
clientProcessingQueue.addListener(handler, receivingWhitelist);
}
// We need a synchronized listener to get the ball rolling
if (autoInject) {
handler.setNullPacketListener(new NullPacketListener(listener));
manager.addPacketListener(handler.getNullPacketListener());
}
return handler;
}
private boolean hasValidWhitelist(ListeningWhitelist whitelist) {
return whitelist != null && whitelist.getWhitelist().size() > 0;
}
@Override
public void unregisterAsyncHandler(AsyncListenerHandler handler) {
if (handler == null)
throw new IllegalArgumentException("listenerToken cannot be NULL");
handler.cancel();
}
// Called by AsyncListenerHandler
void unregisterAsyncHandlerInternal(AsyncListenerHandler handler) {
PacketListener listener = handler.getAsyncListener();
boolean synchronusOK = onMainThread();
// Unregister null packet listeners
if (handler.getNullPacketListener() != null) {
manager.removePacketListener(handler.getNullPacketListener());
}
// Just remove it from the queue(s)
if (hasValidWhitelist(listener.getSendingWhitelist())) {
List<Integer> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
// We're already taking care of this, so don't do anything
if (!cleaningUp)
serverQueue.signalPacketUpdate(removed, synchronusOK);
}
if (hasValidWhitelist(listener.getReceivingWhitelist())) {
List<Integer> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
if (!cleaningUp)
clientQueue.signalPacketUpdate(removed, synchronusOK);
}
}
/**
* Determine if we're running on the main thread.
* @return TRUE if we are, FALSE otherwise.
*/
private boolean onMainThread() {
return Thread.currentThread().getId() == mainThread.getId();
}
@Override
public void unregisterAsyncHandlers(Plugin plugin) {
unregisterAsyncHandlers(serverProcessingQueue, plugin);
unregisterAsyncHandlers(clientProcessingQueue, plugin);
}
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
// Iterate through every packet listener
for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) {
// Remove the listener
if (Objects.equal(listener.getListener().getPlugin(), plugin)) {
unregisterAsyncHandler(listener.getListener());
}
}
}
/**
* Enqueue a packet for asynchronous processing.
* @param syncPacket - synchronous packet event.
* @param asyncMarker - the asynchronous marker to use.
*/
public void enqueueSyncPacket(PacketEvent syncPacket, AsyncMarker asyncMarker) {
PacketEvent newEvent = PacketEvent.fromSynchronous(syncPacket, asyncMarker);
// Start the process
getSendingQueue(syncPacket).enqueue(newEvent);
// We know this is occuring on the main thread, so pass TRUE
getProcessingQueue(syncPacket).enqueue(newEvent, true);
}
@Override
public Set<Integer> getSendingFilters() {
return serverProcessingQueue.keySet();
}
@Override
public Set<Integer> getReceivingFilters() {
return clientProcessingQueue.keySet();
}
/**
* Used to create a default asynchronous task.
* @param plugin - the calling plugin.
* @param runnable - the runnable.
*/
public void scheduleAsyncTask(Plugin plugin, Runnable runnable) {
scheduler.scheduleAsyncDelayedTask(plugin, runnable);
}
@Override
public boolean hasAsynchronousListeners(PacketEvent packet) {
Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketID());
return list != null && list.size() > 0;
}
/**
* Construct a asynchronous marker with all the default values.
* @return Asynchronous marker.
*/
public AsyncMarker createAsyncMarker() {
return createAsyncMarker(AsyncMarker.DEFAULT_SENDING_DELTA, AsyncMarker.DEFAULT_TIMEOUT_DELTA);
}
/**
* Construct an async marker with the given sending priority delta and timeout delta.
* @param sendingDelta - how many packets we're willing to wait.
* @param timeoutDelta - how long (in ms) until the packet expire.
* @return An async marker.
*/
public AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta) {
return createAsyncMarker(sendingDelta, timeoutDelta,
currentSendingIndex.incrementAndGet(), System.currentTimeMillis());
}
// Helper method
private AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta, long sendingIndex, long currentTime) {
return new AsyncMarker(manager, sendingIndex, sendingDelta, System.currentTimeMillis(), timeoutDelta);
}
@Override
public PacketStream getPacketStream() {
return manager;
}
@Override
public Logger getLogger() {
return logger;
}
@Override
public void cleanupAll() {
cleaningUp = true;
serverProcessingQueue.cleanupAll();
serverQueue.cleanupAll();
}
/**
* Signal that a packet is ready to be transmitted.
* @param packet - packet to signal.
*/
public void signalPacketUpdate(PacketEvent packet) {
getSendingQueue(packet).signalPacketUpdate(packet, onMainThread());
}
/**
* Retrieve the sending queue this packet belongs to.
* @param packet - the packet.
* @return The server or client sending queue the packet belongs to.
*/
private PacketSendingQueue getSendingQueue(PacketEvent packet) {
return packet.isServerPacket() ? serverQueue : clientQueue;
}
/**
* Signal that a packet has finished processing.
* @param packet - packet to signal.
*/
public void signalProcessingDone(PacketEvent packet) {
getProcessingQueue(packet).signalProcessingDone();
}
/**
* Retrieve the processing queue this packet belongs to.
* @param packet - the packet.
* @return The server or client sending processing the packet belongs to.
*/
private PacketProcessingQueue getProcessingQueue(PacketEvent packet) {
return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue;
}
/**
* Send any due packets, or clean up packets that have expired.
*/
public void sendProcessedPackets(int tickCounter, boolean onMainThread) {
// The server queue is unlikely to need checking that often
if (tickCounter % 10 == 0) {
serverQueue.trySendPackets(onMainThread);
}
clientQueue.trySendPackets(onMainThread);
}
}
/*
* 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.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.base.Objects;
/**
* Represents a filter manager for asynchronous packets.
*
* @author Kristian
*/
public class AsyncFilterManager implements AsynchronousManager {
private PacketProcessingQueue serverProcessingQueue;
private PacketSendingQueue serverQueue;
private PacketProcessingQueue clientProcessingQueue;
private PacketSendingQueue clientQueue;
private Logger logger;
// The likely main thread
private Thread mainThread;
// Default scheduler
private BukkitScheduler scheduler;
// Our protocol manager
private ProtocolManager manager;
// Current packet index
private AtomicInteger currentSendingIndex = new AtomicInteger();
// Whether or not we're currently cleaning up
private volatile boolean cleaningUp;
public AsyncFilterManager(Logger logger, BukkitScheduler scheduler, ProtocolManager manager) {
// Server packets are synchronized already
this.serverQueue = new PacketSendingQueue(false);
// Client packets must be synchronized
this.clientQueue = new PacketSendingQueue(true);
this.serverProcessingQueue = new PacketProcessingQueue(serverQueue);
this.clientProcessingQueue = new PacketProcessingQueue(clientQueue);
this.scheduler = scheduler;
this.manager = manager;
this.logger = logger;
this.mainThread = Thread.currentThread();
}
@Override
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
return registerAsyncHandler(listener, true);
}
/**
* Registers an asynchronous packet handler.
* <p>
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
* <p>
* Asynchronous events will only be executed if a synchronous listener with the same packets is registered.
* If you already have a synchronous event, call this method with autoInject set to FALSE.
*
* @param listener - the packet listener that will recieve these asynchronous events.
* @param autoInject - whether or not to automatically create the corresponding synchronous listener,
* @return An asynchrouns handler.
*/
public AsyncListenerHandler registerAsyncHandler(PacketListener listener, boolean autoInject) {
AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener);
ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist();
ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist();
// Add listener to either or both processing queue
if (hasValidWhitelist(sendingWhitelist)) {
PacketFilterManager.verifyWhitelist(listener, sendingWhitelist);
serverProcessingQueue.addListener(handler, sendingWhitelist);
}
if (hasValidWhitelist(receivingWhitelist)) {
PacketFilterManager.verifyWhitelist(listener, receivingWhitelist);
clientProcessingQueue.addListener(handler, receivingWhitelist);
}
// We need a synchronized listener to get the ball rolling
if (autoInject) {
handler.setNullPacketListener(new NullPacketListener(listener));
manager.addPacketListener(handler.getNullPacketListener());
}
return handler;
}
private boolean hasValidWhitelist(ListeningWhitelist whitelist) {
return whitelist != null && whitelist.getWhitelist().size() > 0;
}
@Override
public void unregisterAsyncHandler(AsyncListenerHandler handler) {
if (handler == null)
throw new IllegalArgumentException("listenerToken cannot be NULL");
handler.cancel();
}
// Called by AsyncListenerHandler
void unregisterAsyncHandlerInternal(AsyncListenerHandler handler) {
PacketListener listener = handler.getAsyncListener();
boolean synchronusOK = onMainThread();
// Unregister null packet listeners
if (handler.getNullPacketListener() != null) {
manager.removePacketListener(handler.getNullPacketListener());
}
// Just remove it from the queue(s)
if (hasValidWhitelist(listener.getSendingWhitelist())) {
List<Integer> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
// We're already taking care of this, so don't do anything
if (!cleaningUp)
serverQueue.signalPacketUpdate(removed, synchronusOK);
}
if (hasValidWhitelist(listener.getReceivingWhitelist())) {
List<Integer> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
if (!cleaningUp)
clientQueue.signalPacketUpdate(removed, synchronusOK);
}
}
/**
* Determine if we're running on the main thread.
* @return TRUE if we are, FALSE otherwise.
*/
private boolean onMainThread() {
return Thread.currentThread().getId() == mainThread.getId();
}
@Override
public void unregisterAsyncHandlers(Plugin plugin) {
unregisterAsyncHandlers(serverProcessingQueue, plugin);
unregisterAsyncHandlers(clientProcessingQueue, plugin);
}
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
// Iterate through every packet listener
for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) {
// Remove the listener
if (Objects.equal(listener.getListener().getPlugin(), plugin)) {
unregisterAsyncHandler(listener.getListener());
}
}
}
/**
* Enqueue a packet for asynchronous processing.
* @param syncPacket - synchronous packet event.
* @param asyncMarker - the asynchronous marker to use.
*/
public synchronized void enqueueSyncPacket(PacketEvent syncPacket, AsyncMarker asyncMarker) {
PacketEvent newEvent = PacketEvent.fromSynchronous(syncPacket, asyncMarker);
if (asyncMarker.isQueued() || asyncMarker.isTransmitted())
throw new IllegalArgumentException("Cannot queue a packet that has already been queued.");
asyncMarker.setQueuedSendingIndex(asyncMarker.getNewSendingIndex());
// Start the process
getSendingQueue(syncPacket).enqueue(newEvent);
// We know this is occuring on the main thread, so pass TRUE
getProcessingQueue(syncPacket).enqueue(newEvent, true);
}
@Override
public Set<Integer> getSendingFilters() {
return serverProcessingQueue.keySet();
}
@Override
public Set<Integer> getReceivingFilters() {
return clientProcessingQueue.keySet();
}
/**
* Used to create a default asynchronous task.
* @param plugin - the calling plugin.
* @param runnable - the runnable.
*/
public void scheduleAsyncTask(Plugin plugin, Runnable runnable) {
scheduler.scheduleAsyncDelayedTask(plugin, runnable);
}
@Override
public boolean hasAsynchronousListeners(PacketEvent packet) {
Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketID());
return list != null && list.size() > 0;
}
/**
* Construct a asynchronous marker with all the default values.
* @return Asynchronous marker.
*/
public AsyncMarker createAsyncMarker() {
return createAsyncMarker(AsyncMarker.DEFAULT_SENDING_DELTA, AsyncMarker.DEFAULT_TIMEOUT_DELTA);
}
/**
* Construct an async marker with the given sending priority delta and timeout delta.
* @param sendingDelta - how many packets we're willing to wait.
* @param timeoutDelta - how long (in ms) until the packet expire.
* @return An async marker.
*/
public AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta) {
return createAsyncMarker(sendingDelta, timeoutDelta,
currentSendingIndex.incrementAndGet(), System.currentTimeMillis());
}
// Helper method
private AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta, long sendingIndex, long currentTime) {
return new AsyncMarker(manager, sendingIndex, sendingDelta, System.currentTimeMillis(), timeoutDelta);
}
@Override
public PacketStream getPacketStream() {
return manager;
}
@Override
public Logger getLogger() {
return logger;
}
@Override
public void cleanupAll() {
cleaningUp = true;
serverProcessingQueue.cleanupAll();
serverQueue.cleanupAll();
}
@Override
public void signalPacketTransmission(PacketEvent packet) {
signalPacketTransmission(packet, onMainThread());
}
/**
* Signal that a packet is ready to be transmitted.
* @param packet - packet to signal.
* @param onMainThread - whether or not this method was run by the main thread.
*/
private void signalPacketTransmission(PacketEvent packet, boolean onMainThread) {
AsyncMarker marker = packet.getAsyncMarker();
if (marker == null)
throw new IllegalArgumentException(
"A sync packet cannot be transmitted by the asynchronous manager.");
if (!marker.isQueued())
throw new IllegalArgumentException(
"A packet must have been queued before it can be transmitted.");
// Only send if the packet is ready
if (marker.decrementProcessingDelay() == 0) {
getSendingQueue(packet).signalPacketUpdate(packet, onMainThread);
}
}
/**
* Retrieve the sending queue this packet belongs to.
* @param packet - the packet.
* @return The server or client sending queue the packet belongs to.
*/
private PacketSendingQueue getSendingQueue(PacketEvent packet) {
return packet.isServerPacket() ? serverQueue : clientQueue;
}
/**
* Signal that a packet has finished processing.
* @param packet - packet to signal.
*/
public void signalFreeProcessingSlot(PacketEvent packet) {
getProcessingQueue(packet).signalProcessingDone();
}
/**
* Retrieve the processing queue this packet belongs to.
* @param packet - the packet.
* @return The server or client sending processing the packet belongs to.
*/
private PacketProcessingQueue getProcessingQueue(PacketEvent packet) {
return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue;
}
/**
* Send any due packets, or clean up packets that have expired.
*/
public void sendProcessedPackets(int tickCounter, boolean onMainThread) {
// The server queue is unlikely to need checking that often
if (tickCounter % 10 == 0) {
serverQueue.trySendPackets(onMainThread);
}
clientQueue.trySendPackets(onMainThread);
}
}
@@ -1,391 +1,499 @@
package com.comphenix.protocol.async;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
/**
* Represents a handler for an asynchronous event.
*
* @author Kristian
*/
public class AsyncListenerHandler {
/**
* Signal an end to packet processing.
*/
private static final PacketEvent INTERUPT_PACKET = new PacketEvent(new Object());
/**
* Called when the threads have to wake up for something important.
*/
private static final PacketEvent WAKEUP_PACKET = new PacketEvent(new Object());
// Unique worker ID
private static final AtomicInteger nextID = new AtomicInteger();
// Default queue capacity
private static int DEFAULT_CAPACITY = 1024;
// Cancel the async handler
private volatile boolean cancelled;
// Number of worker threads
private final AtomicInteger started = new AtomicInteger();
// The packet listener
private PacketListener listener;
// The filter manager
private AsyncFilterManager filterManager;
private NullPacketListener nullPacketListener;
// List of queued packets
private ArrayBlockingQueue<PacketEvent> queuedPackets = new ArrayBlockingQueue<PacketEvent>(DEFAULT_CAPACITY);
// List of cancelled tasks
private final Set<Integer> stoppedTasks = new HashSet<Integer>();
private final Object stopLock = new Object();
// Minecraft main thread
private Thread mainThread;
public AsyncListenerHandler(Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) {
if (filterManager == null)
throw new IllegalArgumentException("filterManager cannot be NULL");
if (listener == null)
throw new IllegalArgumentException("listener cannot be NULL");
this.mainThread = mainThread;
this.filterManager = filterManager;
this.listener = listener;
}
public boolean isCancelled() {
return cancelled;
}
public PacketListener getAsyncListener() {
return listener;
}
/**
* Set the synchronized listener that has been automatically created.
* @param nullPacketListener - automatically created listener.
*/
void setNullPacketListener(NullPacketListener nullPacketListener) {
this.nullPacketListener = nullPacketListener;
}
/**
* Retrieve the synchronized listener that was automatically created.
* @return Automatically created listener.
*/
PacketListener getNullPacketListener() {
return nullPacketListener;
}
private String getPluginName() {
return PacketAdapter.getPluginName(listener);
}
/**
* Retrieve the plugin associated with this async listener.
* @return The plugin.
*/
public Plugin getPlugin() {
return listener != null ? listener.getPlugin() : null;
}
/**
* Cancel the handler.
*/
public void cancel() {
// Remove the listener as quickly as possible
close();
}
/**
* Queue a packet for processing.
* @param packet - a packet for processing.
* @throws IllegalStateException If the underlying packet queue is full.
*/
public void enqueuePacket(PacketEvent packet) {
if (packet == null)
throw new IllegalArgumentException("packet is NULL");
queuedPackets.add(packet);
}
/**
* Create a worker that will initiate the listener loop. Note that using stop() to
* close a specific worker is less efficient than stopping an arbitrary worker.
* <p>
* <b>Warning</b>: Never call the run() method in the main thread.
*/
public AsyncRunnable getListenerLoop() {
return new AsyncRunnable() {
private final AtomicBoolean firstRun = new AtomicBoolean();
private final AtomicBoolean finished = new AtomicBoolean();
private final int id = nextID.incrementAndGet();
@Override
public int getID() {
return id;
}
@Override
public void run() {
// Careful now
if (firstRun.compareAndSet(false, true)) {
listenerLoop(id);
synchronized (stopLock) {
stoppedTasks.remove(id);
stopLock.notifyAll();
finished.set(true);
}
} else {
if (finished.get())
throw new IllegalStateException(
"This listener has already been run. Create a new instead.");
else
throw new IllegalStateException(
"This listener loop has already been started. Create a new instead.");
}
}
@Override
public boolean stop() throws InterruptedException {
synchronized (stopLock) {
if (!isRunning())
return false;
stoppedTasks.add(id);
// Wake up threads - we have a listener to stop
for (int i = 0; i < getWorkers(); i++) {
queuedPackets.offer(WAKEUP_PACKET);
}
finished.set(true);
waitForStops();
return true;
}
}
@Override
public boolean isRunning() {
return firstRun.get() && !finished.get();
}
@Override
public boolean isFinished() {
return finished.get();
}
};
}
/**
* Start a singler worker thread handling the asynchronous.
*/
public synchronized void start() {
if (listener.getPlugin() == null)
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
if (cancelled)
throw new IllegalStateException("Cannot start a worker when the listener is closing.");
filterManager.scheduleAsyncTask(listener.getPlugin(), getListenerLoop());
}
/**
* Start multiple worker threads for this listener.
* @param count - number of worker threads to start.
*/
public synchronized void start(int count) {
for (int i = 0; i < count; i++)
start();
}
/**
* Stop a worker thread.
*/
public synchronized void stop() {
queuedPackets.add(INTERUPT_PACKET);
}
/**
* Stop the given amount of worker threads.
* @param count - number of threads to stop.
*/
public synchronized void stop(int count) {
for (int i = 0; i < count; i++)
stop();
}
/**
* Set the current number of workers.
* <p>
* This method can only be called with a count of zero when the listener is closing.
* @param count - new number of workers.
*/
public synchronized void setWorkers(int count) {
if (count < 0)
throw new IllegalArgumentException("Number of workers cannot be less than zero.");
if (count > DEFAULT_CAPACITY)
throw new IllegalArgumentException("Cannot initiate more than " + DEFAULT_CAPACITY + " workers");
if (cancelled && count > 0)
throw new IllegalArgumentException("Cannot add workers when the listener is closing.");
long time = System.currentTimeMillis();
// Try to get to the correct count
while (started.get() != count) {
if (started.get() < count)
start();
else
stop();
// May happen if another thread is doing something similar to "setWorkers"
if ((System.currentTimeMillis() - time) > 1000)
throw new RuntimeException("Failed to set worker count.");
}
}
/**
* Retrieve the current number of registered workers.
* <p>
* Note that the returned value may be out of data.
* @return Number of registered workers.
*/
public synchronized int getWorkers() {
return started.get();
}
/**
* Wait until every tasks scheduled to stop has actually stopped.
* @return TRUE if the current listener should stop, FALSE otherwise.
* @throws InterruptedException - If the current thread was interrupted.
*/
private boolean waitForStops() throws InterruptedException {
synchronized (stopLock) {
while (stoppedTasks.size() > 0 && !cancelled) {
stopLock.wait();
}
return cancelled;
}
}
// DO NOT call this method from the main thread
private void listenerLoop(int workerID) {
// Danger, danger!
if (Thread.currentThread().getId() == mainThread.getId())
throw new IllegalStateException("Do not call this method from the main thread.");
if (cancelled)
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
try {
// Wait if certain threads are stopping
if (waitForStops())
return;
// Proceed
started.incrementAndGet();
mainLoop:
while (!cancelled) {
PacketEvent packet = queuedPackets.take();
AsyncMarker marker = packet.getAsyncMarker();
// Handle cancel requests
if (packet == null || marker == null || packet == INTERUPT_PACKET) {
return;
} else if (packet == WAKEUP_PACKET) {
// This is a bit slow, but it should be safe
synchronized (stopLock) {
// Are we the one who is supposed to stop?
if (stoppedTasks.contains(workerID))
return;
if (waitForStops())
return;
}
}
// Here's the core of the asynchronous processing
try {
marker.setListenerHandler(this);
marker.setWorkerID(workerID);
if (packet.isServerPacket())
listener.onPacketSending(packet);
else
listener.onPacketReceiving(packet);
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
filterManager.getLogger().log(Level.SEVERE,
"Unhandled exception occured in onAsyncPacket() for " + getPluginName(), e);
}
// Now, get the next non-cancelled listener
for (; marker.getListenerTraversal().hasNext(); ) {
AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener();
if (!handler.isCancelled()) {
handler.enqueuePacket(packet);
continue mainLoop;
}
}
// There are no more listeners - queue the packet for transmission
filterManager.signalPacketUpdate(packet);
filterManager.signalProcessingDone(packet);
}
} catch (InterruptedException e) {
// We're done
} finally {
// Clean up
started.decrementAndGet();
close();
}
}
private synchronized void close() {
// Remove the listener itself
if (!cancelled) {
filterManager.unregisterAsyncHandlerInternal(this);
cancelled = true;
// Tell every uncancelled thread to end
stopThreads();
}
}
/**
* Use the poision pill method to stop every worker thread.
*/
private void stopThreads() {
// Poison Pill Shutdown
queuedPackets.clear();
stop(started.get());
// Individual shut down is irrelevant now
synchronized (stopLock) {
stopLock.notifyAll();
}
}
}
/*
* 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.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
/**
* Represents a handler for an asynchronous event.
*
* @author Kristian
*/
public class AsyncListenerHandler {
/**
* Signal an end to packet processing.
*/
private static final PacketEvent INTERUPT_PACKET = new PacketEvent(new Object());
/**
* Called when the threads have to wake up for something important.
*/
private static final PacketEvent WAKEUP_PACKET = new PacketEvent(new Object());
// Unique worker ID
private static final AtomicInteger nextID = new AtomicInteger();
// Default queue capacity
private static int DEFAULT_CAPACITY = 1024;
// Cancel the async handler
private volatile boolean cancelled;
// Number of worker threads
private final AtomicInteger started = new AtomicInteger();
// The packet listener
private PacketListener listener;
// The filter manager
private AsyncFilterManager filterManager;
private NullPacketListener nullPacketListener;
// List of queued packets
private ArrayBlockingQueue<PacketEvent> queuedPackets = new ArrayBlockingQueue<PacketEvent>(DEFAULT_CAPACITY);
// List of cancelled tasks
private final Set<Integer> stoppedTasks = new HashSet<Integer>();
private final Object stopLock = new Object();
// Minecraft main thread
private Thread mainThread;
public AsyncListenerHandler(Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) {
if (filterManager == null)
throw new IllegalArgumentException("filterManager cannot be NULL");
if (listener == null)
throw new IllegalArgumentException("listener cannot be NULL");
this.mainThread = mainThread;
this.filterManager = filterManager;
this.listener = listener;
}
public boolean isCancelled() {
return cancelled;
}
public PacketListener getAsyncListener() {
return listener;
}
/**
* Set the synchronized listener that has been automatically created.
* @param nullPacketListener - automatically created listener.
*/
void setNullPacketListener(NullPacketListener nullPacketListener) {
this.nullPacketListener = nullPacketListener;
}
/**
* Retrieve the synchronized listener that was automatically created.
* @return Automatically created listener.
*/
PacketListener getNullPacketListener() {
return nullPacketListener;
}
private String getPluginName() {
return PacketAdapter.getPluginName(listener);
}
/**
* Retrieve the plugin associated with this async listener.
* @return The plugin.
*/
public Plugin getPlugin() {
return listener != null ? listener.getPlugin() : null;
}
/**
* Cancel the handler.
*/
public void cancel() {
// Remove the listener as quickly as possible
close();
}
/**
* Queue a packet for processing.
* @param packet - a packet for processing.
* @throws IllegalStateException If the underlying packet queue is full.
*/
public void enqueuePacket(PacketEvent packet) {
if (packet == null)
throw new IllegalArgumentException("packet is NULL");
queuedPackets.add(packet);
}
/**
* Create a worker that will initiate the listener loop. Note that using stop() to
* close a specific worker is less efficient than stopping an arbitrary worker.
* <p>
* <b>Warning</b>: Never call the run() method in the main thread.
*/
public AsyncRunnable getListenerLoop() {
return new AsyncRunnable() {
private final AtomicBoolean firstRun = new AtomicBoolean();
private final AtomicBoolean finished = new AtomicBoolean();
private final int id = nextID.incrementAndGet();
@Override
public int getID() {
return id;
}
@Override
public void run() {
// Careful now
if (firstRun.compareAndSet(false, true)) {
listenerLoop(id);
synchronized (stopLock) {
stoppedTasks.remove(id);
stopLock.notifyAll();
finished.set(true);
}
} else {
if (finished.get())
throw new IllegalStateException(
"This listener has already been run. Create a new instead.");
else
throw new IllegalStateException(
"This listener loop has already been started. Create a new instead.");
}
}
@Override
public boolean stop() throws InterruptedException {
synchronized (stopLock) {
if (!isRunning())
return false;
stoppedTasks.add(id);
// Wake up threads - we have a listener to stop
for (int i = 0; i < getWorkers(); i++) {
queuedPackets.offer(WAKEUP_PACKET);
}
finished.set(true);
waitForStops();
return true;
}
}
@Override
public boolean isRunning() {
return firstRun.get() && !finished.get();
}
@Override
public boolean isFinished() {
return finished.get();
}
};
}
/**
* Start a singler worker thread handling the asynchronous listener.
*/
public synchronized void start() {
if (listener.getPlugin() == null)
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
if (cancelled)
throw new IllegalStateException("Cannot start a worker when the listener is closing.");
final AsyncRunnable listenerLoop = getListenerLoop();
filterManager.scheduleAsyncTask(listener.getPlugin(), new Runnable() {
@Override
public void run() {
Thread thread = Thread.currentThread();
String previousName = thread.getName();
String workerName = getFriendlyWorkerName(listenerLoop.getID());
// Add the friendly worker name
thread.setName(workerName);
listenerLoop.run();
thread.setName(previousName);
}
});
}
/**
* Start a singler worker thread handling the asynchronous listener.
* <p>
* This method is intended to allow callers to customize the thread priority
* before the worker loop is actually called. This is simpler than to
* schedule the worker threads manually.
* <pre><code>
* listenerHandler.start(new Function&lt;AsyncRunnable, Void&gt;() {
* &#64;Override
* public Void apply(&#64;Nullable AsyncRunnable workerLoop) {
* Thread thread = Thread.currentThread();
* int prevPriority = thread.getPriority();
*
* thread.setPriority(Thread.MIN_PRIORITY);
* workerLoop.run();
* thread.setPriority(prevPriority);
* return null;
* }
* });
* }
* </code></pre>
* @param executor - a method that will execute the given listener loop.
*/
public synchronized void start(Function<AsyncRunnable, Void> executor) {
if (listener.getPlugin() == null)
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
if (cancelled)
throw new IllegalStateException("Cannot start a worker when the listener is closing.");
final AsyncRunnable listenerLoop = getListenerLoop();
final Function<AsyncRunnable, Void> delegateCopy = executor;
filterManager.scheduleAsyncTask(listener.getPlugin(), new Runnable() {
@Override
public void run() {
delegateCopy.apply(listenerLoop);
}
});
}
/**
* Create a friendly thread name using the following convention:
* <p><code>
* &nbsp;&nbsp;&nbsp;&nbsp;Protocol Worker {id} - {plugin} - [recv: {packets}, send: {packets}]
* </code></p>
* @param id - the worker ID.
* @return A friendly thread name.
*/
public String getFriendlyWorkerName(int id) {
return String.format("Protocol Worker #%s - %s - [recv: %s, send: %s]",
id,
PacketAdapter.getPluginName(listener),
fromWhitelist(listener.getReceivingWhitelist()),
fromWhitelist(listener.getSendingWhitelist())
);
}
/**
* Convert the given whitelist to a comma-separated list of packet IDs.
* @param whitelist - the whitelist.
* @return A comma separated list of packet IDs in the whitelist, or the emtpy string.
*/
private String fromWhitelist(ListeningWhitelist whitelist) {
if (whitelist == null)
return "";
else
return Joiner.on(", ").join(whitelist.getWhitelist());
}
/**
* Start multiple worker threads for this listener.
* @param count - number of worker threads to start.
*/
public synchronized void start(int count) {
for (int i = 0; i < count; i++)
start();
}
/**
* Stop a worker thread.
*/
public synchronized void stop() {
queuedPackets.add(INTERUPT_PACKET);
}
/**
* Stop the given amount of worker threads.
* @param count - number of threads to stop.
*/
public synchronized void stop(int count) {
for (int i = 0; i < count; i++)
stop();
}
/**
* Set the current number of workers.
* <p>
* This method can only be called with a count of zero when the listener is closing.
* @param count - new number of workers.
*/
public synchronized void setWorkers(int count) {
if (count < 0)
throw new IllegalArgumentException("Number of workers cannot be less than zero.");
if (count > DEFAULT_CAPACITY)
throw new IllegalArgumentException("Cannot initiate more than " + DEFAULT_CAPACITY + " workers");
if (cancelled && count > 0)
throw new IllegalArgumentException("Cannot add workers when the listener is closing.");
long time = System.currentTimeMillis();
// Try to get to the correct count
while (started.get() != count) {
if (started.get() < count)
start();
else
stop();
// May happen if another thread is doing something similar to "setWorkers"
if ((System.currentTimeMillis() - time) > 50)
throw new RuntimeException("Failed to set worker count.");
}
}
/**
* Retrieve the current number of registered workers.
* <p>
* Note that the returned value may be out of data.
* @return Number of registered workers.
*/
public synchronized int getWorkers() {
return started.get();
}
/**
* Wait until every tasks scheduled to stop has actually stopped.
* @return TRUE if the current listener should stop, FALSE otherwise.
* @throws InterruptedException - If the current thread was interrupted.
*/
private boolean waitForStops() throws InterruptedException {
synchronized (stopLock) {
while (stoppedTasks.size() > 0 && !cancelled) {
stopLock.wait();
}
return cancelled;
}
}
// DO NOT call this method from the main thread
private void listenerLoop(int workerID) {
// Danger, danger!
if (Thread.currentThread().getId() == mainThread.getId())
throw new IllegalStateException("Do not call this method from the main thread.");
if (cancelled)
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
try {
// Wait if certain threads are stopping
if (waitForStops())
return;
// Proceed
started.incrementAndGet();
mainLoop:
while (!cancelled) {
PacketEvent packet = queuedPackets.take();
AsyncMarker marker = packet.getAsyncMarker();
// Handle cancel requests
if (packet == null || marker == null || packet == INTERUPT_PACKET) {
return;
} else if (packet == WAKEUP_PACKET) {
// This is a bit slow, but it should be safe
synchronized (stopLock) {
// Are we the one who is supposed to stop?
if (stoppedTasks.contains(workerID))
return;
if (waitForStops())
return;
}
}
// Here's the core of the asynchronous processing
try {
marker.setListenerHandler(this);
marker.setWorkerID(workerID);
synchronized (marker.getProcessingLock()) {
if (packet.isServerPacket())
listener.onPacketSending(packet);
else
listener.onPacketReceiving(packet);
}
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
filterManager.getLogger().log(Level.SEVERE,
"Unhandled exception occured in onAsyncPacket() for " + getPluginName(), e);
}
// Now, get the next non-cancelled listener
for (; marker.getListenerTraversal().hasNext(); ) {
AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener();
if (!handler.isCancelled()) {
handler.enqueuePacket(packet);
continue mainLoop;
}
}
// There are no more listeners - queue the packet for transmission
filterManager.signalFreeProcessingSlot(packet);
// Note that listeners can opt to delay the packet transmission
filterManager.signalPacketTransmission(packet);
}
} catch (InterruptedException e) {
// We're done
} finally {
// Clean up
started.decrementAndGet();
close();
}
}
private synchronized void close() {
// Remove the listener itself
if (!cancelled) {
filterManager.unregisterAsyncHandlerInternal(this);
cancelled = true;
// Tell every uncancelled thread to end
stopThreads();
}
}
/**
* Use the poision pill method to stop every worker thread.
*/
private void stopThreads() {
// Poison Pill Shutdown
queuedPackets.clear();
stop(started.get());
// Individual shut down is irrelevant now
synchronized (stopLock) {
stopLock.notifyAll();
}
}
}
@@ -1,345 +1,450 @@
package com.comphenix.protocol.async;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import net.minecraft.server.Packet;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.primitives.Longs;
/**
* Contains information about the packet that is being processed by asynchronous listeners.
* <p>
* Asynchronous listeners can use this to set packet timeout or transmission order.
*
* @author Kristian
*/
public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = -2621498096616187384L;
/**
* Default number of milliseconds until a packet will rejected.
*/
public static final int DEFAULT_TIMEOUT_DELTA = 60000;
/**
* Default number of packets to skip.
*/
public static final int DEFAULT_SENDING_DELTA = 0;
/**
* The packet stream responsible for transmitting the packet when it's done processing.
*/
private transient PacketStream packetStream;
/**
* Current list of async packet listeners.
*/
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
// Timeout handling
private long initialTime;
private long timeout;
// Packet order
private long originalSendingIndex;
private long newSendingIndex;
// Whether or not the packet has been processed by the listeners
private volatile boolean processed;
// Whether or not the packet has been sent
private volatile boolean transmitted;
// Whether or not the asynchronous processing itself should be cancelled
private volatile boolean asyncCancelled;
// Used to identify the asynchronous worker
private AsyncListenerHandler listenerHandler;
private int workerID;
// Determine if Minecraft processes this packet asynchronously
private static Method isMinecraftAsync;
private static boolean alwaysSync;
/**
* Create a container for asyncronous packets.
* @param initialTime - the current time in milliseconds since 01.01.1970 00:00.
*/
AsyncMarker(PacketStream packetStream, long sendingIndex, long sendingDelta, long initialTime, long timeoutDelta) {
if (packetStream == null)
throw new IllegalArgumentException("packetStream cannot be NULL");
this.packetStream = packetStream;
// Timeout
this.initialTime = initialTime;
this.timeout = initialTime + timeoutDelta;
// Sending index
this.originalSendingIndex = sendingIndex;
this.newSendingIndex = sendingIndex;
}
/**
* Retrieve the time the packet was initially queued for asynchronous processing.
* @return The initial time in number of milliseconds since 01.01.1970 00:00.
*/
public long getInitialTime() {
return initialTime;
}
/**
* Retrieve the time the packet will be forcefully rejected.
* @return The time to reject the packet, in milliseconds since 01.01.1970 00:00.
*/
public long getTimeout() {
return timeout;
}
/**
* Set the time the packet will be forcefully rejected.
* @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00.
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* Retrieve the order the packet was originally transmitted.
* @return The original packet index.
*/
public long getOriginalSendingIndex() {
return originalSendingIndex;
}
/**
* Retrieve the desired sending order after processing has completed.
* <p>
* Higher sending order means lower priority.
* @return Desired sending order.
*/
public long getNewSendingIndex() {
return newSendingIndex;
}
/**
* Sets the desired sending order after processing has completed.
* <p>
* Higher sending order means lower priority.
* @param newSendingIndex - new packet send index.
*/
public void setNewSendingIndex(long newSendingIndex) {
this.newSendingIndex = newSendingIndex;
}
/**
* Retrieve the packet stream responsible for transmitting this packet.
* @return The packet stream.
*/
public PacketStream getPacketStream() {
return packetStream;
}
/**
* Sets the output packet stream responsible for transmitting this packet.
* @param packetStream - new output packet stream.
*/
public void setPacketStream(PacketStream packetStream) {
this.packetStream = packetStream;
}
/**
* Retrieve whether or not this packet has been processed by the async listeners.
* @return TRUE if it has been processed, FALSE otherwise.
*/
public boolean isProcessed() {
return processed;
}
/**
* Sets whether or not this packet has been processed by the async listeners.
* @param processed - TRUE if it has, FALSE otherwise.
*/
void setProcessed(boolean processed) {
this.processed = processed;
}
/**
* Retrieve whether or not this packet has already been sent.
* @return TRUE if it has been sent before, FALSE otherwise.
*/
public boolean isTransmitted() {
return transmitted;
}
/**
* Determine if this packet has expired.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean hasExpired() {
return hasExpired(System.currentTimeMillis());
}
/**
* Determine if this packet has expired given this time.
* @param currentTime - the current time in milliseconds since 01.01.1970 00:00.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean hasExpired(long currentTime) {
return timeout < currentTime;
}
/**
* Determine if the asynchronous handling should be cancelled.
* @return TRUE if it should, FALSE otherwise.
*/
public boolean isAsyncCancelled() {
return asyncCancelled;
}
/**
* Set whether or not the asynchronous handling should be cancelled.
* @param asyncCancelled - TRUE to cancel it, FALSE otherwise.
*/
public void setAsyncCancelled(boolean asyncCancelled) {
this.asyncCancelled = asyncCancelled;
}
/**
* Retrieve the current asynchronous listener handler.
* @return Asychronous listener handler, or NULL if this packet is not asynchronous.
*/
public AsyncListenerHandler getListenerHandler() {
return listenerHandler;
}
/**
* Set the current asynchronous listener handler.
* <p>
* Used by the worker to update the value.
* @param listenerHandler - new listener handler.
*/
void setListenerHandler(AsyncListenerHandler listenerHandler) {
this.listenerHandler = listenerHandler;
}
/**
* Retrieve the current worker ID.
* @return Current worker ID.
*/
public int getWorkerID() {
return workerID;
}
/**
* Set the current worker ID.
* <p>
* Used by the worker.
* @param workerID - new worker ID.
*/
void setWorkerID(int workerID) {
this.workerID = workerID;
}
/**
* Retrieve iterator for the next listener in line.
* @return Next async packet listener iterator.
*/
Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
return listenerTraversal;
}
/**
* Set the iterator for the next listener.
* @param listenerTraversal - the new async packet listener iterator.
*/
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal) {
this.listenerTraversal = listenerTraversal;
}
/**
* Transmit a given packet to the current packet stream.
* @param event - the packet to send.
* @throws IOException If the packet couldn't be sent.
*/
public void sendPacket(PacketEvent event) throws IOException {
try {
if (event.isServerPacket()) {
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), false);
} else {
packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), false);
}
transmitted = true;
} catch (InvocationTargetException e) {
throw new IOException("Cannot send packet", e);
} catch (IllegalAccessException e) {
throw new IOException("Cannot send packet", e);
}
}
/**
* Determine if Minecraft allows asynchronous processing of this packet.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException {
if (isMinecraftAsync == null && !alwaysSync) {
try {
isMinecraftAsync = FuzzyReflection.fromClass(Packet.class).getMethodByName("a_.*");
} catch (RuntimeException e) {
// This will occur in 1.2.5 (or possibly in later versions)
List<Method> methods = FuzzyReflection.fromClass(Packet.class).
getMethodListByParameters(boolean.class, new Class[] {});
// Try to look for boolean methods
if (methods.size() == 2) {
isMinecraftAsync = methods.get(1);
} else if (methods.size() == 1) {
// We're in 1.2.5
alwaysSync = true;
} else {
System.err.println("Cannot determine asynchronous state of packets!");
alwaysSync = true;
}
}
}
if (alwaysSync) {
return false;
} else {
try {
// Wrap exceptions
return (Boolean) isMinecraftAsync.invoke(event.getPacket().getHandle());
} catch (IllegalArgumentException e) {
throw new FieldAccessException("Illegal argument", e);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Unable to reflect method call 'a_', or: isAsyncPacket.", e);
} catch (InvocationTargetException e) {
throw new FieldAccessException("Minecraft error", e);
}
}
}
@Override
public int compareTo(AsyncMarker o) {
if (o == null)
return 1;
else
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
}
}
/*
* 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.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import net.minecraft.server.Packet;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.primitives.Longs;
/**
* Contains information about the packet that is being processed by asynchronous listeners.
* <p>
* Asynchronous listeners can use this to set packet timeout or transmission order.
*
* @author Kristian
*/
public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = -2621498096616187384L;
/**
* Default number of milliseconds until a packet will rejected.
*/
public static final int DEFAULT_TIMEOUT_DELTA = 60000;
/**
* Default number of packets to skip.
*/
public static final int DEFAULT_SENDING_DELTA = 0;
/**
* The packet stream responsible for transmitting the packet when it's done processing.
*/
private transient PacketStream packetStream;
/**
* Current list of async packet listeners.
*/
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
// Timeout handling
private long initialTime;
private long timeout;
// Packet order
private long originalSendingIndex;
private long newSendingIndex;
// Used to determine if a packet must be reordered in the sending queue
private Long queuedSendingIndex;
// Whether or not the packet has been processed by the listeners
private volatile boolean processed;
// Whether or not the packet has been sent
private volatile boolean transmitted;
// Whether or not the asynchronous processing itself should be cancelled
private volatile boolean asyncCancelled;
// Whether or not to delay processing
private AtomicInteger processingDelay = new AtomicInteger();
// Used to synchronize processing on the shared PacketEvent
private Object processingLock = new Object();
// Used to identify the asynchronous worker
private transient AsyncListenerHandler listenerHandler;
private transient int workerID;
// Determine if Minecraft processes this packet asynchronously
private static Method isMinecraftAsync;
private static boolean alwaysSync;
/**
* Create a container for asyncronous packets.
* @param initialTime - the current time in milliseconds since 01.01.1970 00:00.
*/
AsyncMarker(PacketStream packetStream, long sendingIndex, long sendingDelta, long initialTime, long timeoutDelta) {
if (packetStream == null)
throw new IllegalArgumentException("packetStream cannot be NULL");
this.packetStream = packetStream;
// Timeout
this.initialTime = initialTime;
this.timeout = initialTime + timeoutDelta;
// Sending index
this.originalSendingIndex = sendingIndex;
this.newSendingIndex = sendingIndex;
}
/**
* Retrieve the time the packet was initially queued for asynchronous processing.
* @return The initial time in number of milliseconds since 01.01.1970 00:00.
*/
public long getInitialTime() {
return initialTime;
}
/**
* Retrieve the time the packet will be forcefully rejected.
* @return The time to reject the packet, in milliseconds since 01.01.1970 00:00.
*/
public long getTimeout() {
return timeout;
}
/**
* Set the time the packet will be forcefully rejected.
* @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00.
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* Retrieve the order the packet was originally transmitted.
* @return The original packet index.
*/
public long getOriginalSendingIndex() {
return originalSendingIndex;
}
/**
* Retrieve the desired sending order after processing has completed.
* <p>
* Higher sending order means lower priority.
* @return Desired sending order.
*/
public long getNewSendingIndex() {
return newSendingIndex;
}
/**
* Sets the desired sending order after processing has completed.
* <p>
* Higher sending order means lower priority.
* @param newSendingIndex - new packet send index.
*/
public void setNewSendingIndex(long newSendingIndex) {
this.newSendingIndex = newSendingIndex;
}
/**
* Retrieve the packet stream responsible for transmitting this packet.
* @return The packet stream.
*/
public PacketStream getPacketStream() {
return packetStream;
}
/**
* Sets the output packet stream responsible for transmitting this packet.
* @param packetStream - new output packet stream.
*/
public void setPacketStream(PacketStream packetStream) {
this.packetStream = packetStream;
}
/**
* Retrieve whether or not this packet has been processed by the async listeners.
* @return TRUE if it has been processed, FALSE otherwise.
*/
public boolean isProcessed() {
return processed;
}
/**
* Sets whether or not this packet has been processed by the async listeners.
* @param processed - TRUE if it has, FALSE otherwise.
*/
void setProcessed(boolean processed) {
this.processed = processed;
}
/**
* Increment the number of times this packet must be signalled as done before its transmitted.
* <p>
* This is useful if an asynchronous listener is waiting for further information before the
* packet can be sent to the user. A packet listener <b>MUST</b> eventually call
* {@link AsyncFilterManager#signalPacketTransmission(PacketEvent)},
* even if the packet is cancelled, after this method is called.
* <p>
* It is recommended that processing outside a packet listener is wrapped in a synchronized block
* using the {@link #getProcessingLock()} method.
* <p>
* To decrement the processing delay, call signalPacketUpdate. A thread that calls this method
* multiple times must call signalPacketUpdate at least that many times.
* @return The new processing delay.
*/
public int incrementProcessingDelay() {
return processingDelay.incrementAndGet();
}
/**
* Decrement the number of times this packet must be signalled as done before it's transmitted.
* @return The new processing delay. If zero, the packet should be sent.
*/
int decrementProcessingDelay() {
return processingDelay.decrementAndGet();
}
/**
* Retrieve the number of times a packet must be signalled to be done before it's sent.
* @return Number of processing delays.
*/
public int getProcessingDelay() {
return processingDelay.get();
}
/**
* Whether or not this packet is or has been queued for processing.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isQueued() {
return queuedSendingIndex != null;
}
/**
* Retrieve the sending index when the packet was queued.
* @return Queued sending index.
*/
public long getQueuedSendingIndex() {
return queuedSendingIndex != null ? queuedSendingIndex : 0;
}
/**
* Set the sending index when the packet was queued.
* @param queuedSendingIndex - sending index.
*/
void setQueuedSendingIndex(Long queuedSendingIndex) {
this.queuedSendingIndex = queuedSendingIndex;
}
/**
* Processing lock used to synchronize access to the parent PacketEvent and PacketContainer.
* <p>
* This lock is automatically acquired for every asynchronous packet listener. It should only be
* used to synchronize access to a PacketEvent if it's processing has been delayed.
* @return A processing lock.
*/
public Object getProcessingLock() {
return processingLock;
}
public void setProcessingLock(Object processingLock) {
this.processingLock = processingLock;
}
/**
* Retrieve whether or not this packet has already been sent.
* @return TRUE if it has been sent before, FALSE otherwise.
*/
public boolean isTransmitted() {
return transmitted;
}
/**
* Determine if this packet has expired.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean hasExpired() {
return hasExpired(System.currentTimeMillis());
}
/**
* Determine if this packet has expired given this time.
* @param currentTime - the current time in milliseconds since 01.01.1970 00:00.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean hasExpired(long currentTime) {
return timeout < currentTime;
}
/**
* Determine if the asynchronous handling should be cancelled.
* @return TRUE if it should, FALSE otherwise.
*/
public boolean isAsyncCancelled() {
return asyncCancelled;
}
/**
* Set whether or not the asynchronous handling should be cancelled.
* <p>
* This is only relevant during the synchronous processing. Asynchronous
* listeners should use the normal cancel-field to cancel a PacketEvent.
*
* @param asyncCancelled - TRUE to cancel it, FALSE otherwise.
*/
public void setAsyncCancelled(boolean asyncCancelled) {
this.asyncCancelled = asyncCancelled;
}
/**
* Retrieve the current asynchronous listener handler.
* @return Asychronous listener handler, or NULL if this packet is not asynchronous.
*/
public AsyncListenerHandler getListenerHandler() {
return listenerHandler;
}
/**
* Set the current asynchronous listener handler.
* <p>
* Used by the worker to update the value.
* @param listenerHandler - new listener handler.
*/
void setListenerHandler(AsyncListenerHandler listenerHandler) {
this.listenerHandler = listenerHandler;
}
/**
* Retrieve the current worker ID.
* @return Current worker ID.
*/
public int getWorkerID() {
return workerID;
}
/**
* Set the current worker ID.
* <p>
* Used by the worker.
* @param workerID - new worker ID.
*/
void setWorkerID(int workerID) {
this.workerID = workerID;
}
/**
* Retrieve iterator for the next listener in line.
* @return Next async packet listener iterator.
*/
Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
return listenerTraversal;
}
/**
* Set the iterator for the next listener.
* @param listenerTraversal - the new async packet listener iterator.
*/
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal) {
this.listenerTraversal = listenerTraversal;
}
/**
* Transmit a given packet to the current packet stream.
* @param event - the packet to send.
* @throws IOException If the packet couldn't be sent.
*/
void sendPacket(PacketEvent event) throws IOException {
try {
if (event.isServerPacket()) {
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), false);
} else {
packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), false);
}
transmitted = true;
} catch (InvocationTargetException e) {
throw new IOException("Cannot send packet", e);
} catch (IllegalAccessException e) {
throw new IOException("Cannot send packet", e);
}
}
/**
* Determine if Minecraft allows asynchronous processing of this packet.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException {
if (isMinecraftAsync == null && !alwaysSync) {
try {
isMinecraftAsync = FuzzyReflection.fromClass(Packet.class).getMethodByName("a_.*");
} catch (RuntimeException e) {
// This will occur in 1.2.5 (or possibly in later versions)
List<Method> methods = FuzzyReflection.fromClass(Packet.class).
getMethodListByParameters(boolean.class, new Class[] {});
// Try to look for boolean methods
if (methods.size() == 2) {
isMinecraftAsync = methods.get(1);
} else if (methods.size() == 1) {
// We're in 1.2.5
alwaysSync = true;
} else {
System.err.println("Cannot determine asynchronous state of packets!");
alwaysSync = true;
}
}
}
if (alwaysSync) {
return false;
} else {
try {
// Wrap exceptions
return (Boolean) isMinecraftAsync.invoke(event.getPacket().getHandle());
} catch (IllegalArgumentException e) {
throw new FieldAccessException("Illegal argument", e);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Unable to reflect method call 'a_', or: isAsyncPacket.", e);
} catch (InvocationTargetException e) {
throw new FieldAccessException("Minecraft error", e);
}
}
}
@Override
public int compareTo(AsyncMarker o) {
if (o == null)
return 1;
else
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
}
}
@@ -0,0 +1,52 @@
/*
* 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;
/**
* A runnable representing a asynchronous event listener.
*
* @author Kristian
*/
public interface AsyncRunnable extends Runnable {
/**
* Retrieve a unique worker ID.
* @return Unique worker ID.
*/
public int getID();
/**
* Stop the given runnable.
* <p>
* This may not occur right away.
* @return TRUE if the thread was stopped, FALSE if it was already stopped.
*/
public boolean stop() throws InterruptedException;
/**
* Determine if we're running or not.
* @return TRUE if we're running, FALSE otherwise.
*/
public boolean isRunning();
/**
* Determine if this runnable has already run its course.
* @return TRUE if it has been stopped, FALSE otherwise.
*/
boolean isFinished();
}
@@ -1,62 +1,79 @@
package com.comphenix.protocol.async;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
/**
* Represents a NO OPERATION listener.
*
* @author Kristian
*/
class NullPacketListener implements PacketListener {
private ListeningWhitelist sendingWhitelist;
private ListeningWhitelist receivingWhitelist;
private Plugin plugin;
/**
* Create a no-op listener with the same whitelist and plugin as the given listener.
* @param original - the packet listener to copy.
*/
public NullPacketListener(PacketListener original) {
this.sendingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getSendingWhitelist());
this.receivingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getReceivingWhitelist());
this.plugin = original.getPlugin();
}
@Override
public void onPacketSending(PacketEvent event) {
// NULL
}
@Override
public void onPacketReceiving(PacketEvent event) {
// NULL
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) {
if (whitelist != null)
return new ListeningWhitelist(priority, whitelist.getWhitelist());
else
return null;
}
@Override
public Plugin getPlugin() {
return plugin;
}
}
/*
* 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 org.bukkit.plugin.Plugin;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
/**
* Represents a NO OPERATION listener.
*
* @author Kristian
*/
class NullPacketListener implements PacketListener {
private ListeningWhitelist sendingWhitelist;
private ListeningWhitelist receivingWhitelist;
private Plugin plugin;
/**
* Create a no-op listener with the same whitelist and plugin as the given listener.
* @param original - the packet listener to copy.
*/
public NullPacketListener(PacketListener original) {
this.sendingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getSendingWhitelist());
this.receivingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getReceivingWhitelist());
this.plugin = original.getPlugin();
}
@Override
public void onPacketSending(PacketEvent event) {
// NULL
}
@Override
public void onPacketReceiving(PacketEvent event) {
// NULL
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) {
if (whitelist != null)
return new ListeningWhitelist(priority, whitelist.getWhitelist(), whitelist.getGamePhase());
else
return null;
}
@Override
public Plugin getPlugin() {
return plugin;
}
}
@@ -0,0 +1,59 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.async;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.base.Preconditions;
import com.google.common.collect.ComparisonChain;
/**
* Provides a comparable to a packet event.
*
* @author Kristian
*/
class PacketEventHolder implements Comparable<PacketEventHolder> {
private PacketEvent event;
private long sendingIndex = 0;
/**
* A wrapper that ensures the packet event is ordered by sending index.
* @param event - packet event to wrap.
*/
public PacketEventHolder(PacketEvent event) {
this.event = Preconditions.checkNotNull(event, "Event must be non-null");
if (event.getAsyncMarker() != null)
this.sendingIndex = event.getAsyncMarker().getNewSendingIndex();
}
/**
* Retrieve the stored event.
* @return The stored event.
*/
public PacketEvent getEvent() {
return event;
}
@Override
public int compareTo(PacketEventHolder other) {
return ComparisonChain.start().
compare(sendingIndex, other.sendingIndex).
result();
}
}
@@ -1,152 +1,172 @@
package com.comphenix.protocol.async;
import java.util.Collection;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.Semaphore;
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.collect.MinMaxPriorityQueue;
/**
* Handles the processing of every packet type.
*
* @author Kristian
*/
class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncListenerHandler> {
// Initial number of elements
public static final int INITIAL_CAPACITY = 64;
/**
* Default maximum number of packets to process concurrently.
*/
public static final int DEFAULT_MAXIMUM_CONCURRENCY = 32;
/**
* Default maximum number of packets to queue for processing.
*/
public static final int DEFAULT_QUEUE_LIMIT = 1024 * 60;
/**
* Number of packets we're processing concurrently.
*/
private final int maximumConcurrency;
private Semaphore concurrentProcessing;
// Queued packets for being processed
private Queue<PacketEventHolder> processingQueue;
// Packets for sending
private PacketSendingQueue sendingQueue;
public PacketProcessingQueue(PacketSendingQueue sendingQueue) {
this(sendingQueue, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
}
public PacketProcessingQueue(PacketSendingQueue sendingQueue, int initialSize, int maximumSize, int maximumConcurrency) {
super();
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
expectedSize(initialSize).
maximumSize(maximumSize).
<PacketEventHolder>create(), null);
this.maximumConcurrency = maximumConcurrency;
this.concurrentProcessing = new Semaphore(maximumConcurrency);
this.sendingQueue = sendingQueue;
}
/**
* Enqueue a packet for processing by the asynchronous listeners.
* @param packet - packet to process.
* @param onMainThread - whether or not this is occuring on the main thread.
* @return TRUE if we sucessfully queued the packet, FALSE if the queue ran out if space.
*/
public boolean enqueue(PacketEvent packet, boolean onMainThread) {
try {
processingQueue.add(new PacketEventHolder(packet));
// Begin processing packets
signalBeginProcessing(onMainThread);
return true;
} catch (IllegalStateException e) {
return false;
}
}
/**
* Number of packet events in the queue.
* @return The number of packet events in the queue.
*/
public int size() {
return processingQueue.size();
}
/**
* Called by the current method and each thread to signal that a packet might be ready for processing.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public void signalBeginProcessing(boolean onMainThread) {
while (concurrentProcessing.tryAcquire()) {
PacketEventHolder holder = processingQueue.poll();
// Any packet queued?
if (holder != null) {
PacketEvent packet = holder.getEvent();
AsyncMarker marker = packet.getAsyncMarker();
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketID());
// Yes, removing the marker will cause the chain to stop
if (list != null) {
Iterator<PrioritizedListener<AsyncListenerHandler>> iterator = list.iterator();
if (iterator.hasNext()) {
marker.setListenerTraversal(iterator);
iterator.next().getListener().enqueuePacket(packet);
continue;
}
}
// The packet has no further listeners. Just send it.
sendingQueue.signalPacketUpdate(packet, onMainThread);
signalProcessingDone();
} else {
// No more queued packets.
signalProcessingDone();
return;
}
}
}
/**
* Called when a packet has been processed.
*/
public void signalProcessingDone() {
concurrentProcessing.release();
}
/**
* Retrieve the maximum number of packets to process at any given time.
* @return Number of simultaneous packet to process.
*/
public int getMaximumConcurrency() {
return maximumConcurrency;
}
public void cleanupAll() {
// Cancel all the threads and every listener
for (PrioritizedListener<AsyncListenerHandler> handler : values()) {
if (handler != null) {
handler.getListener().cancel();
}
}
// Remove the rest, just in case
clearListeners();
}
}
/*
* 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.Collection;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.Semaphore;
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.collect.MinMaxPriorityQueue;
/**
* Handles the processing of every packet type.
*
* @author Kristian
*/
class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncListenerHandler> {
// Initial number of elements
public static final int INITIAL_CAPACITY = 64;
/**
* Default maximum number of packets to process concurrently.
*/
public static final int DEFAULT_MAXIMUM_CONCURRENCY = 32;
/**
* Default maximum number of packets to queue for processing.
*/
public static final int DEFAULT_QUEUE_LIMIT = 1024 * 60;
/**
* Number of packets we're processing concurrently.
*/
private final int maximumConcurrency;
private Semaphore concurrentProcessing;
// Queued packets for being processed
private Queue<PacketEventHolder> processingQueue;
// Packets for sending
private PacketSendingQueue sendingQueue;
public PacketProcessingQueue(PacketSendingQueue sendingQueue) {
this(sendingQueue, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
}
public PacketProcessingQueue(PacketSendingQueue sendingQueue, int initialSize, int maximumSize, int maximumConcurrency) {
super();
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
expectedSize(initialSize).
maximumSize(maximumSize).
<PacketEventHolder>create(), null);
this.maximumConcurrency = maximumConcurrency;
this.concurrentProcessing = new Semaphore(maximumConcurrency);
this.sendingQueue = sendingQueue;
}
/**
* Enqueue a packet for processing by the asynchronous listeners.
* @param packet - packet to process.
* @param onMainThread - whether or not this is occuring on the main thread.
* @return TRUE if we sucessfully queued the packet, FALSE if the queue ran out if space.
*/
public boolean enqueue(PacketEvent packet, boolean onMainThread) {
try {
processingQueue.add(new PacketEventHolder(packet));
// Begin processing packets
signalBeginProcessing(onMainThread);
return true;
} catch (IllegalStateException e) {
return false;
}
}
/**
* Number of packet events in the queue.
* @return The number of packet events in the queue.
*/
public int size() {
return processingQueue.size();
}
/**
* Called by the current method and each thread to signal that a packet might be ready for processing.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public void signalBeginProcessing(boolean onMainThread) {
while (concurrentProcessing.tryAcquire()) {
PacketEventHolder holder = processingQueue.poll();
// Any packet queued?
if (holder != null) {
PacketEvent packet = holder.getEvent();
AsyncMarker marker = packet.getAsyncMarker();
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketID());
marker.incrementProcessingDelay();
// Yes, removing the marker will cause the chain to stop
if (list != null) {
Iterator<PrioritizedListener<AsyncListenerHandler>> iterator = list.iterator();
if (iterator.hasNext()) {
marker.setListenerTraversal(iterator);
iterator.next().getListener().enqueuePacket(packet);
continue;
}
}
// The packet has no further listeners. Just send it.
if (marker.decrementProcessingDelay() == 0)
sendingQueue.signalPacketUpdate(packet, onMainThread);
signalProcessingDone();
} else {
// No more queued packets.
signalProcessingDone();
return;
}
}
}
/**
* Called when a packet has been processed.
*/
public void signalProcessingDone() {
concurrentProcessing.release();
}
/**
* Retrieve the maximum number of packets to process at any given time.
* @return Number of simultaneous packet to process.
*/
public int getMaximumConcurrency() {
return maximumConcurrency;
}
public void cleanupAll() {
// Cancel all the threads and every listener
for (PrioritizedListener<AsyncListenerHandler> handler : values()) {
if (handler != null) {
handler.getListener().cancel();
}
}
// Remove the rest, just in case
clearListeners();
}
}
@@ -1,187 +1,225 @@
package com.comphenix.protocol.async;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.PriorityBlockingQueue;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.FieldAccessException;
/**
* Represents packets ready to be transmitted to a client.
* @author Kristian
*/
class PacketSendingQueue {
public static final int INITIAL_CAPACITY = 64;
private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
// Whether or not packet transmission can only occur on the main thread
private final boolean synchronizeMain;
/**
* Number of packet events in the queue.
* @return The number of packet events in the queue.
*/
public int size() {
return sendingQueue.size();
}
/**
* Create a packet sending queue.
* @param synchronizeMain - whether or not to synchronize with the main thread.
*/
public PacketSendingQueue(boolean synchronizeMain) {
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
this.synchronizeMain = synchronizeMain;
}
/**
* Enqueue a packet for sending.
* @param packet
*/
public void enqueue(PacketEvent packet) {
sendingQueue.add(new PacketEventHolder(packet));
}
/**
* Invoked when one of the packets have finished processing.
* @param packetUpdated - the packet that has now been updated.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) {
// Mark this packet as finished
packetUpdated.getAsyncMarker().setProcessed(true);
trySendPackets(onMainThread);
}
/***
* Invoked when a list of packet IDs are no longer associated with any listeners.
* @param packetsRemoved - packets that no longer have any listeners.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public synchronized void signalPacketUpdate(List<Integer> packetsRemoved, boolean onMainThread) {
Set<Integer> lookup = new HashSet<Integer>(packetsRemoved);
// Note that this is O(n), so it might be expensive
for (PacketEventHolder holder : sendingQueue) {
PacketEvent event = holder.getEvent();
if (lookup.contains(event.getPacketID())) {
event.getAsyncMarker().setProcessed(true);
}
}
// This is likely to have changed the situation a bit
trySendPackets(onMainThread);
}
/**
* Attempt to send any remaining packets.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public void trySendPackets(boolean onMainThread) {
// Transmit as many packets as we can
while (true) {
PacketEventHolder holder = sendingQueue.peek();
if (holder != null) {
PacketEvent current = holder.getEvent();
AsyncMarker marker = current.getAsyncMarker();
// Abort if we're not on the main thread
if (synchronizeMain) {
try {
boolean wantAsync = marker.isMinecraftAsync(current);
boolean wantSync = !wantAsync;
// Quit if we haven't fulfilled our promise
if ((onMainThread && wantAsync) || (!onMainThread && wantSync))
return;
} catch (FieldAccessException e) {
e.printStackTrace();
return;
}
}
if (marker.isProcessed() || marker.hasExpired()) {
if (marker.isProcessed() && !current.isCancelled()) {
// Silently skip players that have logged out
if (isOnline(current.getPlayer())) {
sendPacket(current);
}
}
sendingQueue.poll();
continue;
}
}
// Only repeat when packets are removed
break;
}
}
private boolean isOnline(Player player) {
return player != null && player.isOnline();
}
/**
* Send every packet, regardless of the processing state.
*/
private void forceSend() {
while (true) {
PacketEventHolder holder = sendingQueue.poll();
if (holder != null) {
sendPacket(holder.getEvent());
} else {
break;
}
}
}
/**
* Whether or not the packet transmission must synchronize with the main thread.
* @return TRUE if it must, FALSE otherwise.
*/
public boolean isSynchronizeMain() {
return synchronizeMain;
}
/**
* Transmit a packet, if it hasn't already.
* @param event - the packet to transmit.
*/
private void sendPacket(PacketEvent event) {
AsyncMarker marker = event.getAsyncMarker();
try {
// Don't send a packet twice
if (marker != null && !marker.isTransmitted()) {
marker.sendPacket(event);
}
} catch (IOException e) {
// Just print the error
e.printStackTrace();
}
}
/**
* Automatically transmits every delayed packet.
*/
public void cleanupAll() {
// Note that the cleanup itself will always occur on the main thread
forceSend();
}
}
/*
* 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.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.PriorityBlockingQueue;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.reflect.FieldAccessException;
/**
* Represents packets ready to be transmitted to a client.
* @author Kristian
*/
class PacketSendingQueue {
public static final int INITIAL_CAPACITY = 64;
private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
// Whether or not packet transmission can only occur on the main thread
private final boolean synchronizeMain;
/**
* Number of packet events in the queue.
* @return The number of packet events in the queue.
*/
public int size() {
return sendingQueue.size();
}
/**
* Create a packet sending queue.
* @param synchronizeMain - whether or not to synchronize with the main thread.
*/
public PacketSendingQueue(boolean synchronizeMain) {
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
this.synchronizeMain = synchronizeMain;
}
/**
* Enqueue a packet for sending.
* @param packet - packet to queue.
*/
public void enqueue(PacketEvent packet) {
sendingQueue.add(new PacketEventHolder(packet));
}
/**
* Invoked when one of the packets have finished processing.
* @param packetUpdated - the packet that has now been updated.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) {
AsyncMarker marker = packetUpdated.getAsyncMarker();
// Should we reorder the event?
if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex()) {
PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker);
// "Cancel" the original event
packetUpdated.setCancelled(true);
// Enqueue the copy with the new sending index
enqueue(copy);
}
// Mark this packet as finished
marker.setProcessed(true);
trySendPackets(onMainThread);
}
/***
* Invoked when a list of packet IDs are no longer associated with any listeners.
* @param packetsRemoved - packets that no longer have any listeners.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public synchronized void signalPacketUpdate(List<Integer> packetsRemoved, boolean onMainThread) {
Set<Integer> lookup = new HashSet<Integer>(packetsRemoved);
// Note that this is O(n), so it might be expensive
for (PacketEventHolder holder : sendingQueue) {
PacketEvent event = holder.getEvent();
if (lookup.contains(event.getPacketID())) {
event.getAsyncMarker().setProcessed(true);
}
}
// This is likely to have changed the situation a bit
trySendPackets(onMainThread);
}
/**
* Attempt to send any remaining packets.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public void trySendPackets(boolean onMainThread) {
// Transmit as many packets as we can
while (true) {
PacketEventHolder holder = sendingQueue.peek();
if (holder != null) {
PacketEvent current = holder.getEvent();
AsyncMarker marker = current.getAsyncMarker();
// Abort if we're not on the main thread
if (synchronizeMain) {
try {
boolean wantAsync = marker.isMinecraftAsync(current);
boolean wantSync = !wantAsync;
// Quit if we haven't fulfilled our promise
if ((onMainThread && wantAsync) || (!onMainThread && wantSync))
return;
} catch (FieldAccessException e) {
e.printStackTrace();
return;
}
}
if (marker.isProcessed() || marker.hasExpired()) {
if (marker.isProcessed() && !current.isCancelled()) {
// Silently skip players that have logged out
if (isOnline(current.getPlayer())) {
sendPacket(current);
}
}
sendingQueue.poll();
continue;
}
}
// Only repeat when packets are removed
break;
}
}
private boolean isOnline(Player player) {
return player != null && player.isOnline();
}
/**
* Send every packet, regardless of the processing state.
*/
private void forceSend() {
while (true) {
PacketEventHolder holder = sendingQueue.poll();
if (holder != null) {
sendPacket(holder.getEvent());
} else {
break;
}
}
}
/**
* Whether or not the packet transmission must synchronize with the main thread.
* @return TRUE if it must, FALSE otherwise.
*/
public boolean isSynchronizeMain() {
return synchronizeMain;
}
/**
* Transmit a packet, if it hasn't already.
* @param event - the packet to transmit.
*/
private void sendPacket(PacketEvent event) {
AsyncMarker marker = event.getAsyncMarker();
try {
// Don't send a packet twice
if (marker != null && !marker.isTransmitted()) {
marker.sendPacket(event);
}
} catch (PlayerLoggedOutException e) {
System.out.println(String.format(
"Warning: Dropped packet index %s of ID %s",
marker.getOriginalSendingIndex(), event.getPacketID()
));
} catch (IOException e) {
// Just print the error
e.printStackTrace();
}
}
/**
* Automatically transmits every delayed packet.
*/
public void cleanupAll() {
// Note that the cleanup itself will always occur on the main thread
forceSend();
}
}
@@ -1,211 +1,228 @@
package com.comphenix.protocol.async;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Queue;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
/**
* Synchronization views copied from Google Guava.
*
* @author Kristian
*/
class Synchronization {
/**
* Create a synchronized wrapper for the given queue.
* <p>
* This wrapper cannot synchronize the iterator(). Callers are expected
* to synchronize iterators manually.
* @param queue - the queue to synchronize.
* @param mutex - synchronization mutex, or NULL to use the queue.
* @return A synchronization wrapper.
*/
public static <E> Queue<E> queue(Queue<E> queue, @Nullable Object mutex) {
return (queue instanceof SynchronizedQueue) ?
queue :
new SynchronizedQueue<E>(queue, mutex);
}
private static class SynchronizedObject implements Serializable {
private static final long serialVersionUID = -4408866092364554628L;
final Object delegate;
final Object mutex;
SynchronizedObject(Object delegate, @Nullable Object mutex) {
this.delegate = Preconditions.checkNotNull(delegate);
this.mutex = (mutex == null) ? this : mutex;
}
Object delegate() {
return delegate;
}
// No equals and hashCode; see ForwardingObject for details.
@Override
public String toString() {
synchronized (mutex) {
return delegate.toString();
}
}
}
private static class SynchronizedCollection<E> extends SynchronizedObject implements Collection<E> {
private static final long serialVersionUID = 5440572373531285692L;
private SynchronizedCollection(Collection<E> delegate,
@Nullable Object mutex) {
super(delegate, mutex);
}
@SuppressWarnings("unchecked")
@Override
Collection<E> delegate() {
return (Collection<E>) super.delegate();
}
@Override
public boolean add(E e) {
synchronized (mutex) {
return delegate().add(e);
}
}
@Override
public boolean addAll(Collection<? extends E> c) {
synchronized (mutex) {
return delegate().addAll(c);
}
}
@Override
public void clear() {
synchronized (mutex) {
delegate().clear();
}
}
@Override
public boolean contains(Object o) {
synchronized (mutex) {
return delegate().contains(o);
}
}
@Override
public boolean containsAll(Collection<?> c) {
synchronized (mutex) {
return delegate().containsAll(c);
}
}
@Override
public boolean isEmpty() {
synchronized (mutex) {
return delegate().isEmpty();
}
}
@Override
public Iterator<E> iterator() {
return delegate().iterator(); // manually synchronized
}
@Override
public boolean remove(Object o) {
synchronized (mutex) {
return delegate().remove(o);
}
}
@Override
public boolean removeAll(Collection<?> c) {
synchronized (mutex) {
return delegate().removeAll(c);
}
}
@Override
public boolean retainAll(Collection<?> c) {
synchronized (mutex) {
return delegate().retainAll(c);
}
}
@Override
public int size() {
synchronized (mutex) {
return delegate().size();
}
}
@Override
public Object[] toArray() {
synchronized (mutex) {
return delegate().toArray();
}
}
@Override
public <T> T[] toArray(T[] a) {
synchronized (mutex) {
return delegate().toArray(a);
}
}
}
private static class SynchronizedQueue<E> extends SynchronizedCollection<E> implements Queue<E> {
private static final long serialVersionUID = 1961791630386791902L;
SynchronizedQueue(Queue<E> delegate, @Nullable Object mutex) {
super(delegate, mutex);
}
@Override
Queue<E> delegate() {
return (Queue<E>) super.delegate();
}
@Override
public E element() {
synchronized (mutex) {
return delegate().element();
}
}
@Override
public boolean offer(E e) {
synchronized (mutex) {
return delegate().offer(e);
}
}
@Override
public E peek() {
synchronized (mutex) {
return delegate().peek();
}
}
@Override
public E poll() {
synchronized (mutex) {
return delegate().poll();
}
}
@Override
public E remove() {
synchronized (mutex) {
return delegate().remove();
}
}
}
}
/*
* 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.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Queue;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
/**
* Synchronization views copied from Google Guava.
*
* @author Kristian
*/
class Synchronization {
/**
* Create a synchronized wrapper for the given queue.
* <p>
* This wrapper cannot synchronize the iterator(). Callers are expected
* to synchronize iterators manually.
* @param queue - the queue to synchronize.
* @param mutex - synchronization mutex, or NULL to use the queue.
* @return A synchronization wrapper.
*/
public static <E> Queue<E> queue(Queue<E> queue, @Nullable Object mutex) {
return (queue instanceof SynchronizedQueue) ?
queue :
new SynchronizedQueue<E>(queue, mutex);
}
private static class SynchronizedObject implements Serializable {
private static final long serialVersionUID = -4408866092364554628L;
final Object delegate;
final Object mutex;
SynchronizedObject(Object delegate, @Nullable Object mutex) {
this.delegate = Preconditions.checkNotNull(delegate);
this.mutex = (mutex == null) ? this : mutex;
}
Object delegate() {
return delegate;
}
// No equals and hashCode; see ForwardingObject for details.
@Override
public String toString() {
synchronized (mutex) {
return delegate.toString();
}
}
}
private static class SynchronizedCollection<E> extends SynchronizedObject implements Collection<E> {
private static final long serialVersionUID = 5440572373531285692L;
private SynchronizedCollection(Collection<E> delegate,
@Nullable Object mutex) {
super(delegate, mutex);
}
@SuppressWarnings("unchecked")
@Override
Collection<E> delegate() {
return (Collection<E>) super.delegate();
}
@Override
public boolean add(E e) {
synchronized (mutex) {
return delegate().add(e);
}
}
@Override
public boolean addAll(Collection<? extends E> c) {
synchronized (mutex) {
return delegate().addAll(c);
}
}
@Override
public void clear() {
synchronized (mutex) {
delegate().clear();
}
}
@Override
public boolean contains(Object o) {
synchronized (mutex) {
return delegate().contains(o);
}
}
@Override
public boolean containsAll(Collection<?> c) {
synchronized (mutex) {
return delegate().containsAll(c);
}
}
@Override
public boolean isEmpty() {
synchronized (mutex) {
return delegate().isEmpty();
}
}
@Override
public Iterator<E> iterator() {
return delegate().iterator(); // manually synchronized
}
@Override
public boolean remove(Object o) {
synchronized (mutex) {
return delegate().remove(o);
}
}
@Override
public boolean removeAll(Collection<?> c) {
synchronized (mutex) {
return delegate().removeAll(c);
}
}
@Override
public boolean retainAll(Collection<?> c) {
synchronized (mutex) {
return delegate().retainAll(c);
}
}
@Override
public int size() {
synchronized (mutex) {
return delegate().size();
}
}
@Override
public Object[] toArray() {
synchronized (mutex) {
return delegate().toArray();
}
}
@Override
public <T> T[] toArray(T[] a) {
synchronized (mutex) {
return delegate().toArray(a);
}
}
}
private static class SynchronizedQueue<E> extends SynchronizedCollection<E> implements Queue<E> {
private static final long serialVersionUID = 1961791630386791902L;
SynchronizedQueue(Queue<E> delegate, @Nullable Object mutex) {
super(delegate, mutex);
}
@Override
Queue<E> delegate() {
return (Queue<E>) super.delegate();
}
@Override
public E element() {
synchronized (mutex) {
return delegate().element();
}
}
@Override
public boolean offer(E e) {
synchronized (mutex) {
return delegate().offer(e);
}
}
@Override
public E peek() {
synchronized (mutex) {
return delegate().peek();
}
}
@Override
public E poll() {
synchronized (mutex) {
return delegate().poll();
}
}
@Override
public E remove() {
synchronized (mutex) {
return delegate().remove();
}
}
}
}
@@ -1,131 +1,148 @@
package com.comphenix.protocol.concurrency;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.collect.Iterables;
/**
* A thread-safe implementation of a listener multimap.
*
* @author Kristian
*/
public abstract class AbstractConcurrentListenerMultimap<TListener> {
// The core of our map
private ConcurrentMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> listeners =
new ConcurrentHashMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>();
/**
* Adds a listener to its requested list of packet recievers.
* @param listener - listener with a list of packets to recieve notifcations for.
* @param whitelist - the packet whitelist to use.
*/
public void addListener(TListener listener, ListeningWhitelist whitelist) {
PrioritizedListener<TListener> prioritized = new PrioritizedListener<TListener>(listener, whitelist.getPriority());
for (Integer packetID : whitelist.getWhitelist()) {
addListener(packetID, prioritized);
}
}
// Add the listener to a specific packet notifcation list
private void addListener(Integer packetID, PrioritizedListener<TListener> listener) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
// We don't want to create this for every lookup
if (list == null) {
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
// which is a essential feature for our purposes.
final SortedCopyOnWriteArray<PrioritizedListener<TListener>> value = new SortedCopyOnWriteArray<PrioritizedListener<TListener>>();
list = listeners.putIfAbsent(packetID, value);
// We may end up creating multiple multisets, but we'll agree
// on the one to use.
if (list == null) {
list = value;
}
}
// Thread safe
list.add(listener);
}
/**
* Removes the given listener from the packet event list.
* @param listener - listener to remove.
* @param whitelist - the packet whitelist that was used.
* @return Every packet ID that was removed due to no listeners.
*/
public List<Integer> removeListener(TListener listener, ListeningWhitelist whitelist) {
List<Integer> removedPackets = new ArrayList<Integer>();
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
for (Integer packetID : whitelist.getWhitelist()) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
// Remove any listeners
if (list != null) {
// Don't remove from newly created lists
if (list.size() > 0) {
// Remove this listener. Note that priority is generally ignored.
list.remove(new PrioritizedListener<TListener>(listener, whitelist.getPriority()));
if (list.size() == 0) {
listeners.remove(packetID);
removedPackets.add(packetID);
}
}
}
// Move on to the next
}
return removedPackets;
}
/**
* Retrieve the registered listeners, in order from the lowest to the highest priority.
* <p>
* The returned list is thread-safe and doesn't require synchronization.
* @param packetID - packet ID.
* @return Registered listeners.
*/
public Collection<PrioritizedListener<TListener>> getListener(int packetID) {
return listeners.get(packetID);
}
/**
* Retrieve every listener.
* @return Every listener.
*/
public Iterable<PrioritizedListener<TListener>> values() {
return Iterables.concat(listeners.values());
}
/**
* Retrieve every registered packet ID:
* @return Registered packet ID.
*/
public Set<Integer> keySet() {
return listeners.keySet();
}
/**
* Remove all packet listeners.
*/
protected void clearListeners() {
listeners.clear();
}
}
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.collect.Iterables;
/**
* A thread-safe implementation of a listener multimap.
*
* @author Kristian
*/
public abstract class AbstractConcurrentListenerMultimap<TListener> {
// The core of our map
private ConcurrentMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> listeners =
new ConcurrentHashMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>();
/**
* Adds a listener to its requested list of packet recievers.
* @param listener - listener with a list of packets to recieve notifcations for.
* @param whitelist - the packet whitelist to use.
*/
public void addListener(TListener listener, ListeningWhitelist whitelist) {
PrioritizedListener<TListener> prioritized = new PrioritizedListener<TListener>(listener, whitelist.getPriority());
for (Integer packetID : whitelist.getWhitelist()) {
addListener(packetID, prioritized);
}
}
// Add the listener to a specific packet notifcation list
private void addListener(Integer packetID, PrioritizedListener<TListener> listener) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
// We don't want to create this for every lookup
if (list == null) {
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
// which is a essential feature for our purposes.
final SortedCopyOnWriteArray<PrioritizedListener<TListener>> value = new SortedCopyOnWriteArray<PrioritizedListener<TListener>>();
list = listeners.putIfAbsent(packetID, value);
// We may end up creating multiple multisets, but we'll agree
// on the one to use.
if (list == null) {
list = value;
}
}
// Thread safe
list.add(listener);
}
/**
* Removes the given listener from the packet event list.
* @param listener - listener to remove.
* @param whitelist - the packet whitelist that was used.
* @return Every packet ID that was removed due to no listeners.
*/
public List<Integer> removeListener(TListener listener, ListeningWhitelist whitelist) {
List<Integer> removedPackets = new ArrayList<Integer>();
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
for (Integer packetID : whitelist.getWhitelist()) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
// Remove any listeners
if (list != null) {
// Don't remove from newly created lists
if (list.size() > 0) {
// Remove this listener. Note that priority is generally ignored.
list.remove(new PrioritizedListener<TListener>(listener, whitelist.getPriority()));
if (list.size() == 0) {
listeners.remove(packetID);
removedPackets.add(packetID);
}
}
}
// Move on to the next
}
return removedPackets;
}
/**
* Retrieve the registered listeners, in order from the lowest to the highest priority.
* <p>
* The returned list is thread-safe and doesn't require synchronization.
* @param packetID - packet ID.
* @return Registered listeners.
*/
public Collection<PrioritizedListener<TListener>> getListener(int packetID) {
return listeners.get(packetID);
}
/**
* Retrieve every listener.
* @return Every listener.
*/
public Iterable<PrioritizedListener<TListener>> values() {
return Iterables.concat(listeners.values());
}
/**
* Retrieve every registered packet ID:
* @return Registered packet ID.
*/
public Set<Integer> keySet() {
return listeners.keySet();
}
/**
* Remove all packet listeners.
*/
protected void clearListeners() {
listeners.clear();
}
}
@@ -1,223 +1,240 @@
package com.comphenix.protocol.concurrency;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
/**
* An implicitly sorted array list that preserves insertion order and maintains duplicates.
* @param <T> - type of the elements in the list.
*/
public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Iterable<T>, Collection<T> {
// Prevent reordering
private volatile List<T> list;
/**
* Construct an empty sorted array.
*/
public SortedCopyOnWriteArray() {
list = new ArrayList<T>();
}
/**
* Create a sorted array from the given list. The elements will be automatically sorted.
* @param wrapped - the collection whose elements are to be placed into the list.
*/
public SortedCopyOnWriteArray(Collection<T> wrapped) {
this.list = new ArrayList<T>(wrapped);
}
/**
* Create a sorted array from the given list.
* @param wrapped - the collection whose elements are to be placed into the list.
* @param sort - TRUE to automatically sort the collection, FALSE if it is already sorted.
*/
public SortedCopyOnWriteArray(Collection<T> wrapped, boolean sort) {
this.list = new ArrayList<T>(wrapped);
if (sort) {
Collections.sort(list);
}
}
/**
* Inserts the given element in the proper location.
* @param value - element to insert.
*/
@Override
public synchronized boolean add(T value) {
// We use NULL as a special marker, so we don't allow it
if (value == null)
throw new IllegalArgumentException("value cannot be NULL");
List<T> copy = new ArrayList<T>();
for (T element : list) {
// If the value is now greater than the current element, it should be placed right before it
if (value != null && value.compareTo(element) < 0) {
copy.add(value);
value = null;
}
copy.add(element);
}
// Don't forget to add it
if (value != null)
copy.add(value);
list = copy;
return true;
}
@Override
public synchronized boolean addAll(Collection<? extends T> values) {
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
// Insert the new content and sort it
copy.addAll(list);
copy.addAll(values);
Collections.sort(copy);
list = copy;
return true;
}
/**
* Removes from the list by making a new list with every element except the one given.
* <p>
* Objects will be compared using the given objects equals() method.
* @param value - value to remove.
*/
@Override
public synchronized boolean remove(Object value) {
List<T> copy = new ArrayList<T>();
// Note that there's not much to be gained from using BinarySearch, as we
// have to copy (and thus read) the entire list regardless.
// Copy every element except the one given to us.
for (T element : list) {
if (value != null && !Objects.equal(value, element)) {
copy.add(element);
value = null;
}
}
list = copy;
return value == null;
}
@Override
public boolean removeAll(Collection<?> values) {
// Special cases
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
copy.addAll(list);
copy.removeAll(values);
list = copy;
return true;
}
@Override
public boolean retainAll(Collection<?> values) {
// Special cases
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
copy.addAll(list);
copy.removeAll(values);
list = copy;
return true;
}
/**
* Removes from the list by making a copy of every element except the one with the given index.
* @param index - index of the element to remove.
*/
public synchronized void remove(int index) {
List<T> copy = new ArrayList<T>(list);
copy.remove(index);
list = copy;
}
/**
* Retrieves an element by index.
* @param index - index of element to retrieve.
* @return The element at the given location.
*/
public T get(int index) {
return list.get(index);
}
/**
* Retrieve the size of the list.
* @return Size of the list.
*/
public int size() {
return list.size();
}
/**
* Retrieves an iterator over the elements in the given list.
* Warning: No not attempt to remove elements using the iterator.
*/
public Iterator<T> iterator() {
return Iterables.unmodifiableIterable(list).iterator();
}
@Override
public void clear() {
list = new ArrayList<T>();
}
@Override
public boolean contains(Object value) {
return list.contains(value);
}
@Override
public boolean containsAll(Collection<?> values) {
return list.containsAll(values);
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public Object[] toArray() {
return list.toArray();
}
@SuppressWarnings("hiding")
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
}
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
/**
* An implicitly sorted array list that preserves insertion order and maintains duplicates.
* @param <T> - type of the elements in the list.
*/
public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Iterable<T>, Collection<T> {
// Prevent reordering
private volatile List<T> list;
/**
* Construct an empty sorted array.
*/
public SortedCopyOnWriteArray() {
list = new ArrayList<T>();
}
/**
* Create a sorted array from the given list. The elements will be automatically sorted.
* @param wrapped - the collection whose elements are to be placed into the list.
*/
public SortedCopyOnWriteArray(Collection<T> wrapped) {
this.list = new ArrayList<T>(wrapped);
}
/**
* Create a sorted array from the given list.
* @param wrapped - the collection whose elements are to be placed into the list.
* @param sort - TRUE to automatically sort the collection, FALSE if it is already sorted.
*/
public SortedCopyOnWriteArray(Collection<T> wrapped, boolean sort) {
this.list = new ArrayList<T>(wrapped);
if (sort) {
Collections.sort(list);
}
}
/**
* Inserts the given element in the proper location.
* @param value - element to insert.
*/
@Override
public synchronized boolean add(T value) {
// We use NULL as a special marker, so we don't allow it
if (value == null)
throw new IllegalArgumentException("value cannot be NULL");
List<T> copy = new ArrayList<T>();
for (T element : list) {
// If the value is now greater than the current element, it should be placed right before it
if (value != null && value.compareTo(element) < 0) {
copy.add(value);
value = null;
}
copy.add(element);
}
// Don't forget to add it
if (value != null)
copy.add(value);
list = copy;
return true;
}
@Override
public synchronized boolean addAll(Collection<? extends T> values) {
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
// Insert the new content and sort it
copy.addAll(list);
copy.addAll(values);
Collections.sort(copy);
list = copy;
return true;
}
/**
* Removes from the list by making a new list with every element except the one given.
* <p>
* Objects will be compared using the given objects equals() method.
* @param value - value to remove.
*/
@Override
public synchronized boolean remove(Object value) {
List<T> copy = new ArrayList<T>();
// Note that there's not much to be gained from using BinarySearch, as we
// have to copy (and thus read) the entire list regardless.
// Copy every element except the one given to us.
for (T element : list) {
if (value != null && !Objects.equal(value, element)) {
copy.add(element);
value = null;
}
}
list = copy;
return value == null;
}
@Override
public boolean removeAll(Collection<?> values) {
// Special cases
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
copy.addAll(list);
copy.removeAll(values);
list = copy;
return true;
}
@Override
public boolean retainAll(Collection<?> values) {
// Special cases
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
copy.addAll(list);
copy.removeAll(values);
list = copy;
return true;
}
/**
* Removes from the list by making a copy of every element except the one with the given index.
* @param index - index of the element to remove.
*/
public synchronized void remove(int index) {
List<T> copy = new ArrayList<T>(list);
copy.remove(index);
list = copy;
}
/**
* Retrieves an element by index.
* @param index - index of element to retrieve.
* @return The element at the given location.
*/
public T get(int index) {
return list.get(index);
}
/**
* Retrieve the size of the list.
* @return Size of the list.
*/
public int size() {
return list.size();
}
/**
* Retrieves an iterator over the elements in the given list.
* Warning: No not attempt to remove elements using the iterator.
*/
public Iterator<T> iterator() {
return Iterables.unmodifiableIterable(list).iterator();
}
@Override
public void clear() {
list = new ArrayList<T>();
}
@Override
public boolean contains(Object value) {
return list.contains(value);
}
@Override
public boolean containsAll(Collection<?> values) {
return list.containsAll(values);
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public Object[] toArray() {
return list.toArray();
}
@SuppressWarnings("hiding")
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
}
@@ -1,48 +1,48 @@
/*
* 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;
/**
* Used to set a packet filter.
*
* @author Kristian
*/
public enum ConnectionSide {
/**
* Listen for server side packets that will invoke onPacketSending().
*/
SERVER_SIDE,
/**
* Listen for client side packets that will invoke onPacketReceiving().
*/
CLIENT_SIDE,
/**
* Listen for both client and server side packets.
*/
BOTH;
public boolean isForClient() {
return this == CLIENT_SIDE || this == BOTH;
}
public boolean isForServer() {
return this == SERVER_SIDE || this == BOTH;
}
}
/*
* 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;
/**
* Used to set a packet filter.
*
* @author Kristian
*/
public enum ConnectionSide {
/**
* Listen for server side packets that will invoke onPacketSending().
*/
SERVER_SIDE,
/**
* Listen for client side packets that will invoke onPacketReceiving().
*/
CLIENT_SIDE,
/**
* Listen for both client and server side packets.
*/
BOTH;
public boolean isForClient() {
return this == CLIENT_SIDE || this == BOTH;
}
public boolean isForServer() {
return this == SERVER_SIDE || this == BOTH;
}
}
@@ -1,68 +1,68 @@
/*
* 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;
/**
* Represents a packet event priority, similar to the Bukkit EventPriority.
*
* @author Kristian
*/
public enum ListenerPriority {
/**
* Event call is of very low importance and should be ran first, to allow
* other plugins to further customise the outcome.
*/
LOWEST(0),
/**
* Event call is of low importance.
*/
LOW(1),
/**
* Event call is neither important or unimportant, and may be ran normally.
*/
NORMAL(2),
/**
* Event call is of high importance.
*/
HIGH(3),
/**
* Event call is critical and must have the final say in what happens to the
* event.
*/
HIGHEST(4),
/**
* Event is listened to purely for monitoring the outcome of an event.
* <p/>
* No modifications to the event should be made under this priority.
*/
MONITOR(5);
private final int slot;
private ListenerPriority(int slot) {
this.slot = slot;
}
/**
* A low slot represents a low priority.
* @return Integer representation of this priorty.
*/
public int getSlot() {
return slot;
}
}
/*
* 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;
/**
* Represents a packet event priority, similar to the Bukkit EventPriority.
*
* @author Kristian
*/
public enum ListenerPriority {
/**
* Event call is of very low importance and should be ran first, to allow
* other plugins to further customise the outcome.
*/
LOWEST(0),
/**
* Event call is of low importance.
*/
LOW(1),
/**
* Event call is neither important or unimportant, and may be ran normally.
*/
NORMAL(2),
/**
* Event call is of high importance.
*/
HIGH(3),
/**
* Event call is critical and must have the final say in what happens to the
* event.
*/
HIGHEST(4),
/**
* Event is listened to purely for monitoring the outcome of an event.
* <p/>
* No modifications to the event should be made under this priority.
*/
MONITOR(5);
private final int slot;
private ListenerPriority(int slot) {
this.slot = slot;
}
/**
* A low slot represents a low priority.
* @return Integer representation of this priorty.
*/
public int getSlot() {
return slot;
}
}
@@ -1,127 +1,161 @@
/*
* 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.Set;
import com.google.common.base.Objects;
import com.google.common.collect.Sets;
/**
* Determines which packets will be observed by a listener, and with what priority.
* @author Kristian
*/
public class ListeningWhitelist {
/**
* A whitelist with no packets - indicates that the listener shouldn't observe any packets.
*/
public static ListeningWhitelist EMPTY_WHITELIST = new ListeningWhitelist(ListenerPriority.LOW);
private ListenerPriority priority;
private Set<Integer> whitelist;
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* @param priority - the listener priority.
* @param whitelist - set of IDs to observe/enable.
*/
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist) {
this.priority = priority;
this.whitelist = whitelist;
}
/**
* Creates a packet whitelist of a given priority for a list of packets.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
*/
public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) {
this.priority = priority;
this.whitelist = Sets.newHashSet(whitelist);
}
/**
* Whether or not this whitelist has any enabled packets.
* @return TRUE if there are any packets, FALSE otherwise.
*/
public boolean isEnabled() {
return whitelist != null && whitelist.size() > 0;
}
/**
* Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last.
* @return Execution order in terms of priority.
*/
public ListenerPriority getPriority() {
return priority;
}
/**
* Retrieves the list of packets that will be observed by the listeners.
* @return Packet whitelist.
*/
public Set<Integer> getWhitelist() {
return whitelist;
}
@Override
public int hashCode(){
return Objects.hashCode(priority, whitelist);
}
/**
* Determine if any of the given IDs can be found in the whitelist.
* @param whitelist - whitelist to test.
* @param idList - list of packet IDs to find.
* @return TRUE if any of the packets in the list can be found in the whitelist, FALSE otherwise.
*/
public static boolean containsAny(ListeningWhitelist whitelist, int... idList) {
if (whitelist != null) {
for (int i = 0; i < idList.length; i++) {
if (whitelist.getWhitelist().contains(idList[i]))
return true;
}
}
return false;
}
@Override
public boolean equals(final Object obj){
if(obj instanceof ListeningWhitelist){
final ListeningWhitelist other = (ListeningWhitelist) obj;
return Objects.equal(priority, other.priority)
&& Objects.equal(whitelist, other.whitelist);
} else{
return false;
}
}
@Override
public String toString() {
if (this == EMPTY_WHITELIST)
return "EMPTY_WHITELIST";
else
return Objects.toStringHelper(this)
.add("priority", priority)
.add("packets", whitelist).toString();
}
}
/*
* 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.Set;
import com.comphenix.protocol.injector.GamePhase;
import com.google.common.base.Objects;
import com.google.common.collect.Sets;
/**
* Determines which packets will be observed by a listener, and with what priority.
* @author Kristian
*/
public class ListeningWhitelist {
/**
* A whitelist with no packets - indicates that the listener shouldn't observe any packets.
*/
public static final ListeningWhitelist EMPTY_WHITELIST = new ListeningWhitelist(ListenerPriority.LOW);
private ListenerPriority priority;
private Set<Integer> whitelist;
private GamePhase gamePhase;
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* @param priority - the listener priority.
* @param whitelist - set of IDs to observe/enable.
*/
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist) {
this(priority, whitelist, GamePhase.PLAYING);
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* @param priority - the listener priority.
* @param whitelist - set of IDs to observe/enable.
* @param gamePhase - which game phase to receieve notifications on.
*/
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist, GamePhase gamePhase) {
this.priority = priority;
this.whitelist = whitelist;
this.gamePhase = gamePhase;
}
/**
* Creates a packet whitelist of a given priority for a list of packets.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
*/
public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) {
this.priority = priority;
this.whitelist = Sets.newHashSet(whitelist);
this.gamePhase = GamePhase.PLAYING;
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
* @param gamePhase - which game phase to receieve notifications on.
*/
public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase) {
this.priority = priority;
this.whitelist = Sets.newHashSet(whitelist);
this.gamePhase = gamePhase;
}
/**
* Whether or not this whitelist has any enabled packets.
* @return TRUE if there are any packets, FALSE otherwise.
*/
public boolean isEnabled() {
return whitelist != null && whitelist.size() > 0;
}
/**
* Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last.
* @return Execution order in terms of priority.
*/
public ListenerPriority getPriority() {
return priority;
}
/**
* Retrieves the list of packets that will be observed by the listeners.
* @return Packet whitelist.
*/
public Set<Integer> getWhitelist() {
return whitelist;
}
/**
* Retrieve which game phase this listener is active under.
* @return The active game phase.
*/
public GamePhase getGamePhase() {
return gamePhase;
}
@Override
public int hashCode(){
return Objects.hashCode(priority, whitelist, gamePhase);
}
/**
* Determine if any of the given IDs can be found in the whitelist.
* @param whitelist - whitelist to test.
* @param idList - list of packet IDs to find.
* @return TRUE if any of the packets in the list can be found in the whitelist, FALSE otherwise.
*/
public static boolean containsAny(ListeningWhitelist whitelist, int... idList) {
if (whitelist != null) {
for (int i = 0; i < idList.length; i++) {
if (whitelist.getWhitelist().contains(idList[i]))
return true;
}
}
return false;
}
@Override
public boolean equals(final Object obj){
if(obj instanceof ListeningWhitelist){
final ListeningWhitelist other = (ListeningWhitelist) obj;
return Objects.equal(priority, other.priority)
&& Objects.equal(whitelist, other.whitelist);
} else{
return false;
}
}
@Override
public String toString() {
if (this == EMPTY_WHITELIST)
return "EMPTY_WHITELIST";
else
return Objects.toStringHelper(this)
.add("priority", priority)
.add("packets", whitelist).toString();
}
}
@@ -0,0 +1,81 @@
package com.comphenix.protocol.events;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.reflect.FieldAccessException;
/**
* Represents a listener that is notified of every sent and recieved packet.
*
* @author Kristian
*/
public abstract class MonitorAdapter implements PacketListener {
private Plugin plugin;
private ListeningWhitelist sending = ListeningWhitelist.EMPTY_WHITELIST;
private ListeningWhitelist receiving = ListeningWhitelist.EMPTY_WHITELIST;
public MonitorAdapter(Plugin plugin, ConnectionSide side) {
initialize(plugin, side, getLogger(plugin));
}
public MonitorAdapter(Plugin plugin, ConnectionSide side, Logger logger) {
initialize(plugin, side, logger);
}
private void initialize(Plugin plugin, ConnectionSide side, Logger logger) {
this.plugin = plugin;
// Recover in case something goes wrong
try {
if (side.isForServer())
this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getSupported(), GamePhase.BOTH);
if (side.isForClient())
this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getSupported(), GamePhase.BOTH);
} catch (FieldAccessException e) {
if (side.isForServer())
this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getRegistry().values(), GamePhase.BOTH);
if (side.isForClient())
this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getRegistry().values(), GamePhase.BOTH);
logger.log(Level.WARNING, "Defaulting to 1.3 packets.", e);
}
}
/**
* Retrieve a logger, even if we're running in a CraftBukkit version that doesn't support it.
* @param plugin - the plugin to retrieve.
* @return The logger.
*/
private Logger getLogger(Plugin plugin) {
try {
return plugin.getLogger();
} catch (NoSuchMethodError e) {
return Logger.getLogger("Minecraft");
}
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sending;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receiving;
}
@Override
public Plugin getPlugin() {
return plugin;
}
}
@@ -1,124 +1,197 @@
/*
* 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 org.bukkit.plugin.Plugin;
/**
* Represents a packet listener with useful constructors.
*
* @author Kristian
*/
public abstract class PacketAdapter implements PacketListener {
protected Plugin plugin;
protected ConnectionSide connectionSide;
protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
protected ListeningWhitelist sendingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
/**
* Initialize a packet listener with default priority.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param packets - the packet IDs the listener is looking for.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, packets);
}
/**
* Initialize a packet listener for a single connection side.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param packets - the packet IDs the listener is looking for.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Integer... packets) {
if (plugin == null)
throw new IllegalArgumentException("plugin cannot be null");
if (connectionSide == null)
throw new IllegalArgumentException("connectionSide cannot be null");
if (listenerPriority == null)
throw new IllegalArgumentException("listenerPriority cannot be null");
if (packets == null)
throw new IllegalArgumentException("packets cannot be null");
// Add whitelists
if (connectionSide.isForServer())
sendingWhitelist = new ListeningWhitelist(listenerPriority, packets);
if (connectionSide.isForClient())
receivingWhitelist = new ListeningWhitelist(listenerPriority, packets);
this.plugin = plugin;
this.connectionSide = connectionSide;
}
@Override
public void onPacketReceiving(PacketEvent event) {
// Default is to do nothing
}
@Override
public void onPacketSending(PacketEvent event) {
// And here too
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public Plugin getPlugin() {
return plugin;
}
/**
* Retrieves the name of the plugin that has been associated with the listener.
* @return Name of the associated plugin.
*/
public static String getPluginName(PacketListener listener) {
Plugin plugin = listener.getPlugin();
// Try to get the plugin name
try {
if (plugin == null)
return "UNKNOWN";
else
return plugin.getName();
} catch (NoSuchMethodError e) {
return plugin.toString();
}
}
@Override
public String toString() {
// This is used by the error reporter
return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]",
getPluginName(this),
sendingWhitelist,
receivingWhitelist);
}
}
/*
* 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.Set;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.injector.GamePhase;
/**
* Represents a packet listener with useful constructors.
* <p>
* Remember to override onPacketReceiving() and onPacketSending(), depending on the ConnectionSide.
* @author Kristian
*/
public abstract class PacketAdapter implements PacketListener {
protected Plugin plugin;
protected ConnectionSide connectionSide;
protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
protected ListeningWhitelist sendingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
/**
* Initialize a packet listener with default priority.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param packets - the packet IDs the listener is looking for.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, packets);
}
/**
* Initialize a packet listener for a single connection side.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param packets - the packet IDs the listener is looking for.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Set<Integer> packets) {
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets.toArray(new Integer[0]));
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optmize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Set<Integer> packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, gamePhase, packets.toArray(new Integer[0]));
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optmize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Set<Integer> packets) {
this(plugin, connectionSide, listenerPriority, gamePhase, packets.toArray(new Integer[0]));
}
/**
* Initialize a packet listener for a single connection side.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param packets - the packet IDs the listener is looking for.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Integer... packets) {
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets);
}
/**
* Initialize a packet listener for a single connection side.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, GamePhase.PLAYING, packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optmize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Integer... packets) {
if (plugin == null)
throw new IllegalArgumentException("plugin cannot be null");
if (connectionSide == null)
throw new IllegalArgumentException("connectionSide cannot be null");
if (listenerPriority == null)
throw new IllegalArgumentException("listenerPriority cannot be null");
if (gamePhase == null)
throw new IllegalArgumentException("gamePhase cannot be NULL");
if (packets == null)
throw new IllegalArgumentException("packets cannot be null");
// Add whitelists
if (connectionSide.isForServer())
sendingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase);
if (connectionSide.isForClient())
receivingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase);
this.plugin = plugin;
this.connectionSide = connectionSide;
}
@Override
public void onPacketReceiving(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
}
@Override
public void onPacketSending(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public Plugin getPlugin() {
return plugin;
}
/**
* Retrieves the name of the plugin that has been associated with the listener.
* @return Name of the associated plugin.
*/
public static String getPluginName(PacketListener listener) {
Plugin plugin = listener.getPlugin();
// Try to get the plugin name
try {
if (plugin == null)
return "UNKNOWN";
else
return plugin.getName();
} catch (NoSuchMethodError e) {
return plugin.toString();
}
}
@Override
public String toString() {
// This is used by the error reporter
return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]",
getPluginName(this),
sendingWhitelist,
receivingWhitelist);
}
}
@@ -1,365 +1,414 @@
/*
* 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.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.InvocationTargetException;
import java.lang.reflect.Method;
import org.bukkit.World;
import org.bukkit.WorldType;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
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.StructureModifier;
import net.minecraft.server.Packet;
/**
* Represents a Minecraft packet indirectly.
*
* @author Kristian
*/
public class PacketContainer implements Serializable {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 2074805748222377230L;
protected int id;
protected transient Packet handle;
// Current structure modifier
protected transient StructureModifier<Object> structureModifier;
// Check whether or not certain classes exists
private static boolean hasWorldType = false;
// The getEntity method
private static Method getEntity;
// Support for serialization
private static Method writeMethod;
private static Method readMethod;
static {
try {
Class.forName("net.minecraft.server.WorldType");
hasWorldType = true;
} catch (ClassNotFoundException e) {
}
}
/**
* Creates a packet container for a new packet.
* @param id - ID of the packet to create.
*/
public PacketContainer(int id) {
this(id, StructureCache.newPacket(id));
}
/**
* Creates a packet container for an existing packet.
* @param id - ID of the given packet.
* @param handle - contained packet.
*/
public PacketContainer(int id, Packet handle) {
this(id, handle, StructureCache.getStructure(id).withTarget(handle));
}
/**
* Creates a packet container for an existing packet.
* @param id - ID of the given packet.
* @param handle - contained packet.
* @param structure - structure modifier.
*/
public PacketContainer(int id, Packet handle, StructureModifier<Object> structure) {
if (handle == null)
throw new IllegalArgumentException("handle cannot be null.");
this.id = id;
this.handle = handle;
this.structureModifier = structure;
}
/**
* Retrieves the underlying Minecraft packet.
* @return Underlying Minecraft packet.
*/
public Packet getHandle() {
return handle;
}
/**
* Retrieves the generic structure modifier for this packet.
* @return Structure modifier.
*/
public StructureModifier<Object> getModifier() {
return structureModifier;
}
/**
* Retrieves a read/write structure for every field with the given type.
* @param primitiveType - the type to find.
* @return A modifier for this specific type.
*/
public <T> StructureModifier<T> getSpecificModifier(Class<T> primitiveType) {
return structureModifier.withType(primitiveType);
}
/**
* Retrieves a read/write structure for ItemStack.
* <p>
* This modifier will automatically marshall between the Bukkit ItemStack and the
* internal Minecraft ItemStack.
* @return A modifier for ItemStack fields.
*/
public StructureModifier<ItemStack> getItemModifier() {
// Convert from and to the Bukkit wrapper
return structureModifier.<ItemStack>withType(net.minecraft.server.ItemStack.class, new EquivalentConverter<ItemStack>() {
public Object getGeneric(ItemStack specific) {
return ((CraftItemStack) specific).getHandle();
}
@Override
public ItemStack getSpecific(Object generic) {
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
}
@Override
public Class<ItemStack> getSpecificType() {
return ItemStack.class;
}
});
}
/**
* Retrieves a read/write structure for arrays of ItemStacks.
* <p>
* This modifier will automatically marshall between the Bukkit ItemStack and the
* internal Minecraft ItemStack.
* @return A modifier for ItemStack array fields.
*/
public StructureModifier<ItemStack[]> getItemArrayModifier() {
// Convert to and from the Bukkit wrapper
return structureModifier.<ItemStack[]>withType(net.minecraft.server.ItemStack[].class, new EquivalentConverter<ItemStack[]>() {
public Object getGeneric(ItemStack[] specific) {
net.minecraft.server.ItemStack[] result = new net.minecraft.server.ItemStack[specific.length];
// Unwrap every item
for (int i = 0; i < result.length; i++) {
result[i] = ((CraftItemStack) specific[i]).getHandle();
}
return result;
}
@Override
public ItemStack[] getSpecific(Object generic) {
net.minecraft.server.ItemStack[] input = (net.minecraft.server.ItemStack[]) generic;
ItemStack[] result = new ItemStack[input.length];
// Add the wrapper
for (int i = 0; i < result.length; i++) {
result[i] = new CraftItemStack(input[i]);
}
return result;
}
@Override
public Class<ItemStack[]> getSpecificType() {
return ItemStack[].class;
}
});
}
/**
* Retrieves a read/write structure for the world type enum.
* <p>
* This modifier will automatically marshall between the Bukkit world type and the
* internal Minecraft world type.
* @return A modifier for world type fields.
*/
public StructureModifier<WorldType> getWorldTypeModifier() {
if (!hasWorldType) {
// We couldn't find the Minecraft equivalent
return structureModifier.withType(null);
}
// Convert to and from the Bukkit wrapper
return structureModifier.<WorldType>withType(net.minecraft.server.WorldType.class, new EquivalentConverter<WorldType>() {
@Override
public Object getGeneric(WorldType specific) {
return net.minecraft.server.WorldType.getType(specific.getName());
}
@Override
public WorldType getSpecific(Object generic) {
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic;
return WorldType.getByName(type.name());
}
@Override
public Class<WorldType> getSpecificType() {
return WorldType.class;
}
});
}
/**
* Retrieves a read/write structure for entity objects.
* <p>
* Note that entities are transmitted by integer ID, and the type may not be enough
* to distinguish between entities and other values. Thus, this structure modifier
* MAY return null or invalid entities for certain fields. Using the correct index
* is essential.
*
* @return A modifier entity types.
*/
public StructureModifier<Entity> getEntityModifier(World world) {
final Object worldServer = ((CraftWorld) world).getHandle();
final Class<?> nmsEntityClass = net.minecraft.server.Entity.class;
final World worldCopy = world;
if (getEntity == null)
getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters(
"getEntity", nmsEntityClass, new Class[] { int.class });
// Convert to and from the Bukkit wrapper
return structureModifier.<Entity>withType(int.class, new EquivalentConverter<Entity>() {
@Override
public Object getGeneric(Entity specific) {
// Simple enough
return specific.getEntityId();
}
@Override
public Entity getSpecific(Object generic) {
try {
net.minecraft.server.Entity nmsEntity = (net.minecraft.server.Entity)
getEntity.invoke(worldServer, generic);
Integer id = (Integer) generic;
// Attempt to get the Bukkit entity
if (nmsEntity != null) {
return nmsEntity.getBukkitEntity();
} else {
// Maybe it's a player that's just logged in? Try a search
for (Player player : worldCopy.getPlayers()) {
if (player.getEntityId() == id) {
return player;
}
}
System.out.println("Entity doesn't exist.");
return null;
}
} catch (IllegalArgumentException e) {
throw new RuntimeException("Incorrect arguments detected.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot read field due to a security limitation.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error occured in Minecraft method.", e.getCause());
}
}
@Override
public Class<Entity> getSpecificType() {
return Entity.class;
}
});
}
/**
* Retrieves the ID of this packet.
* @return Packet ID.
*/
public int getID() {
return id;
}
private void writeObject(ObjectOutputStream output) throws IOException {
// Default serialization
output.defaultWriteObject();
// We'll take care of NULL packets as well
output.writeBoolean(handle != null);
// Retrieve the write method by reflection
if (writeMethod == null)
writeMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("write", DataOutputStream.class);
try {
// Call the write-method
writeMethod.invoke(handle, new DataOutputStream(output));
} catch (IllegalArgumentException e) {
throw new IOException("Minecraft packet doesn't support DataOutputStream", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Insufficient security privileges.", e);
} catch (InvocationTargetException e) {
throw new IOException("Could not serialize Minecraft packet.", e);
}
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
// Default deserialization
input.defaultReadObject();
// Get structure modifier
structureModifier = StructureCache.getStructure(id);
// Don't read NULL packets
if (input.readBoolean()) {
// Create a default instance of the packet
handle = StructureCache.newPacket(id);
// Retrieve the read method by reflection
if (readMethod == null)
readMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("read", DataInputStream.class);
// Call the read method
try {
readMethod.invoke(handle, new DataInputStream(input));
} catch (IllegalArgumentException e) {
throw new IOException("Minecraft packet doesn't support DataInputStream", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Insufficient security privileges.", e);
} catch (InvocationTargetException e) {
throw new IOException("Could not deserialize Minecraft packet.", e);
}
// And we're done
structureModifier = structureModifier.withTarget(handle);
}
}
}
/*
* 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.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.InvocationTargetException;
import java.lang.reflect.Method;
import org.bukkit.World;
import org.bukkit.WorldType;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
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.StructureModifier;
import net.minecraft.server.Packet;
/**
* Represents a Minecraft packet indirectly.
*
* @author Kristian
*/
public class PacketContainer implements Serializable {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 2074805748222377230L;
protected int id;
protected transient Packet handle;
// Current structure modifier
protected transient StructureModifier<Object> structureModifier;
// Check whether or not certain classes exists
private static boolean hasWorldType = false;
// The getEntity method
private static Method getEntity;
// Support for serialization
private static Method writeMethod;
private static Method readMethod;
static {
try {
Class.forName("net.minecraft.server.WorldType");
hasWorldType = true;
} catch (ClassNotFoundException e) {
}
}
/**
* Creates a packet container for a new packet.
* @param id - ID of the packet to create.
*/
public PacketContainer(int id) {
this(id, StructureCache.newPacket(id));
}
/**
* Creates a packet container for an existing packet.
* @param id - ID of the given packet.
* @param handle - contained packet.
*/
public PacketContainer(int id, Packet handle) {
this(id, handle, StructureCache.getStructure(id).withTarget(handle));
}
/**
* Creates a packet container for an existing packet.
* @param id - ID of the given packet.
* @param handle - contained packet.
* @param structure - structure modifier.
*/
public PacketContainer(int id, Packet handle, StructureModifier<Object> structure) {
if (handle == null)
throw new IllegalArgumentException("handle cannot be null.");
this.id = id;
this.handle = handle;
this.structureModifier = structure;
}
/**
* Retrieves the underlying Minecraft packet.
* @return Underlying Minecraft packet.
*/
public Packet getHandle() {
return handle;
}
/**
* Retrieves the generic structure modifier for this packet.
* @return Structure modifier.
*/
public StructureModifier<Object> getModifier() {
return structureModifier;
}
/**
* Retrieves a read/write structure for every field with the given type.
* @param primitiveType - the type to find.
* @return A modifier for this specific type.
*/
public <T> StructureModifier<T> getSpecificModifier(Class<T> primitiveType) {
return structureModifier.withType(primitiveType);
}
/**
* Retrieves a read/write structure for ItemStack.
* <p>
* This modifier will automatically marshall between the Bukkit ItemStack and the
* internal Minecraft ItemStack.
* @return A modifier for ItemStack fields.
*/
public StructureModifier<ItemStack> getItemModifier() {
// Convert to and from the Bukkit wrapper
return structureModifier.<ItemStack>withType(net.minecraft.server.ItemStack.class,
getIgnoreNull(new EquivalentConverter<ItemStack>() {
public Object getGeneric(ItemStack specific) {
return toStackNMS(specific);
}
@Override
public ItemStack getSpecific(Object generic) {
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
}
@Override
public Class<ItemStack> getSpecificType() {
return ItemStack.class;
}
}));
}
/**
* Retrieves a read/write structure for arrays of ItemStacks.
* <p>
* This modifier will automatically marshall between the Bukkit ItemStack and the
* internal Minecraft ItemStack.
* @return A modifier for ItemStack array fields.
*/
public StructureModifier<ItemStack[]> getItemArrayModifier() {
// Convert to and from the Bukkit wrapper
return structureModifier.<ItemStack[]>withType(
net.minecraft.server.ItemStack[].class,
getIgnoreNull(new EquivalentConverter<ItemStack[]>() {
public Object getGeneric(ItemStack[] specific) {
net.minecraft.server.ItemStack[] result = new net.minecraft.server.ItemStack[specific.length];
// Unwrap every item
for (int i = 0; i < result.length; i++) {
result[i] = toStackNMS(specific[i]);
}
return result;
}
@Override
public ItemStack[] getSpecific(Object generic) {
net.minecraft.server.ItemStack[] input = (net.minecraft.server.ItemStack[]) generic;
ItemStack[] result = new ItemStack[input.length];
// Add the wrapper
for (int i = 0; i < result.length; i++) {
result[i] = new CraftItemStack(input[i]);
}
return result;
}
@Override
public Class<ItemStack[]> getSpecificType() {
return ItemStack[].class;
}
}));
}
/**
* Convert an item stack to the NMS equivalent.
* @param stack - Bukkit stack to convert.
* @return A bukkit stack.
*/
private net.minecraft.server.ItemStack toStackNMS(ItemStack stack) {
// We must be prepared for an object that simply implements ItemStcak
if (stack instanceof CraftItemStack) {
return ((CraftItemStack) stack).getHandle();
} else {
return (new CraftItemStack(stack)).getHandle();
}
}
/**
* Retrieves a read/write structure for the world type enum.
* <p>
* This modifier will automatically marshall between the Bukkit world type and the
* internal Minecraft world type.
* @return A modifier for world type fields.
*/
public StructureModifier<WorldType> getWorldTypeModifier() {
if (!hasWorldType) {
// We couldn't find the Minecraft equivalent
return structureModifier.withType(null);
}
// Convert to and from the Bukkit wrapper
return structureModifier.<WorldType>withType(
net.minecraft.server.WorldType.class,
getIgnoreNull(new EquivalentConverter<WorldType>() {
@Override
public Object getGeneric(WorldType specific) {
return net.minecraft.server.WorldType.getType(specific.getName());
}
@Override
public WorldType getSpecific(Object generic) {
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic;
return WorldType.getByName(type.name());
}
@Override
public Class<WorldType> getSpecificType() {
return WorldType.class;
}
}));
}
/**
* Retrieves a read/write structure for entity objects.
* <p>
* Note that entities are transmitted by integer ID, and the type may not be enough
* to distinguish between entities and other values. Thus, this structure modifier
* MAY return null or invalid entities for certain fields. Using the correct index
* is essential.
*
* @return A modifier entity types.
*/
public StructureModifier<Entity> getEntityModifier(World world) {
final Object worldServer = ((CraftWorld) world).getHandle();
final Class<?> nmsEntityClass = net.minecraft.server.Entity.class;
final World worldCopy = world;
if (getEntity == null)
getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters(
"getEntity", nmsEntityClass, new Class[] { int.class });
// Convert to and from the Bukkit wrapper
return structureModifier.<Entity>withType(
int.class,
getIgnoreNull(new EquivalentConverter<Entity>() {
@Override
public Object getGeneric(Entity specific) {
// Simple enough
return specific.getEntityId();
}
@Override
public Entity getSpecific(Object generic) {
try {
net.minecraft.server.Entity nmsEntity = (net.minecraft.server.Entity)
getEntity.invoke(worldServer, generic);
Integer id = (Integer) generic;
// Attempt to get the Bukkit entity
if (nmsEntity != null) {
return nmsEntity.getBukkitEntity();
} else {
// Maybe it's a player that's just logged in? Try a search
for (Player player : worldCopy.getPlayers()) {
if (player.getEntityId() == id) {
return player;
}
}
System.out.println("Entity doesn't exist.");
return null;
}
} catch (IllegalArgumentException e) {
throw new RuntimeException("Incorrect arguments detected.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot read field due to a security limitation.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error occured in Minecraft method.", e.getCause());
}
}
@Override
public Class<Entity> getSpecificType() {
return Entity.class;
}
}));
}
private <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) {
// Automatically wrap all parameters to the delegate with a NULL check
return new EquivalentConverter<TType>() {
public Object getGeneric(TType specific) {
if (specific != null)
return delegate.getGeneric(specific);
else
return null;
}
@Override
public TType getSpecific(Object generic) {
if (generic != null)
return delegate.getSpecific(generic);
else
return null;
}
@Override
public Class<TType> getSpecificType() {
return delegate.getSpecificType();
}
};
}
/**
* Retrieves the ID of this packet.
* @return Packet ID.
*/
public int getID() {
return id;
}
private void writeObject(ObjectOutputStream output) throws IOException {
// Default serialization
output.defaultWriteObject();
// We'll take care of NULL packets as well
output.writeBoolean(handle != null);
// Retrieve the write method by reflection
if (writeMethod == null)
writeMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("write", DataOutputStream.class);
try {
// Call the write-method
writeMethod.invoke(handle, new DataOutputStream(output));
} catch (IllegalArgumentException e) {
throw new IOException("Minecraft packet doesn't support DataOutputStream", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Insufficient security privileges.", e);
} catch (InvocationTargetException e) {
throw new IOException("Could not serialize Minecraft packet.", e);
}
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
// Default deserialization
input.defaultReadObject();
// Get structure modifier
structureModifier = StructureCache.getStructure(id);
// Don't read NULL packets
if (input.readBoolean()) {
// Create a default instance of the packet
handle = StructureCache.newPacket(id);
// Retrieve the read method by reflection
if (readMethod == null)
readMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("read", DataInputStream.class);
// Call the read method
try {
readMethod.invoke(handle, new DataInputStream(input));
} catch (IllegalArgumentException e) {
throw new IOException("Minecraft packet doesn't support DataInputStream", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Insufficient security privileges.", e);
} catch (InvocationTargetException e) {
throw new IOException("Could not deserialize Minecraft packet.", e);
}
// And we're done
structureModifier = structureModifier.withTarget(handle);
}
}
}
@@ -1,214 +1,214 @@
/*
* 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.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.EventObject;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import com.comphenix.protocol.async.AsyncMarker;
public class PacketEvent extends EventObject implements Cancellable {
/**
* Automatically generated by Eclipse.
*/
private static final long serialVersionUID = -5360289379097430620L;
private transient Player player;
private PacketContainer packet;
private boolean serverPacket;
private boolean cancel;
private AsyncMarker asyncMarker;
private boolean asynchronous;
/**
* Use the static constructors to create instances of this event.
* @param source - the event source.
*/
public PacketEvent(Object source) {
super(source);
}
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
super(source);
this.packet = packet;
this.player = player;
this.serverPacket = serverPacket;
}
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
super(origial.source);
this.packet = origial.packet;
this.player = origial.player;
this.cancel = origial.cancel;
this.serverPacket = origial.serverPacket;
this.asyncMarker = asyncMarker;
this.asynchronous = true;
}
/**
* Creates an event representing a client packet transmission.
* @param source - the event source.
* @param packet - the packet.
* @param client - the client that sent the packet.
* @return The event.
*/
public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) {
return new PacketEvent(source, packet, client, false);
}
/**
* Creates an event representing a server packet transmission.
* @param source - the event source.
* @param packet - the packet.
* @param recipient - the client that will receieve the packet.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
return new PacketEvent(source, packet, recipient, true);
}
/**
* Create an asynchronous packet event from a synchronous event and a async marker.
* @param event - the original synchronous event.
* @param marker - the asynchronous marker.
* @return The new packet event.
*/
public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) {
return new PacketEvent(event, marker);
}
/**
* Retrieves the packet that will be sent to the player.
* @return Packet to send to the player.
*/
public PacketContainer getPacket() {
return packet;
}
/**
* Replace the packet that will be sent to the player.
* @param packet - the packet that will be sent instead.
*/
public void setPacket(PacketContainer packet) {
this.packet = packet;
}
/**
* Retrieves the packet ID.
* @return The current packet ID.
*/
public int getPacketID() {
return packet.getID();
}
/**
* Retrieves whether or not the packet should be cancelled.
* @return TRUE if it should be cancelled, FALSE otherwise.
*/
public boolean isCancelled() {
return cancel;
}
/**
* Sets whether or not the packet should be cancelled.
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
*/
public void setCancelled(boolean cancel) {
this.cancel = cancel;
}
/**
* Retrieves the player that has sent the packet or is recieving it.
* @return The player associated with this event.
*/
public Player getPlayer() {
return player;
}
/**
* Whether or not this packet was created by the server.
* <p>
* Most listeners can deduce this by noting which listener method was invoked.
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
*/
public boolean isServerPacket() {
return serverPacket;
}
/**
* Retrieve the asynchronous marker.
* <p>
* If the packet is synchronous, this marker will be used to schedule an asynchronous event. In the following
* asynchronous event, the marker is used to correctly pass the packet around to the different threads.
* <p>
* Note that if there are no asynchronous events that can receive this packet, the marker is NULL.
* @return The current asynchronous marker, or NULL.
*/
public AsyncMarker getAsyncMarker() {
return asyncMarker;
}
/**
* Set the asynchronous marker.
* <p>
* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled
* to be processed asynchronously with the given settings.
* <p>
* Note that if there are no asynchronous events that can receive this packet, the marker should be NULL.
* @param asyncMarker - the new asynchronous marker, or NULL.
* @throws IllegalStateException If the current event is asynchronous.
*/
public void setAsyncMarker(AsyncMarker asyncMarker) {
if (isAsynchronous())
throw new IllegalStateException("The marker is immutable for asynchronous events");
this.asyncMarker = asyncMarker;
}
/**
* Determine if the packet event has been executed asynchronously or not.
* @return TRUE if this packet event is asynchronous, FALSE otherwise.
*/
public boolean isAsynchronous() {
return asynchronous;
}
private void writeObject(ObjectOutputStream output) throws IOException {
// Default serialization
output.defaultWriteObject();
// Write the name of the player (or NULL if it's not set)
output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null);
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
// Default deserialization
input.defaultReadObject();
final SerializedOfflinePlayer offlinePlayer = (SerializedOfflinePlayer) input.readObject();
if (offlinePlayer != null) {
// Better than nothing
player = offlinePlayer.getPlayer();
}
}
}
/*
* 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.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.EventObject;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import com.comphenix.protocol.async.AsyncMarker;
public class PacketEvent extends EventObject implements Cancellable {
/**
* Automatically generated by Eclipse.
*/
private static final long serialVersionUID = -5360289379097430620L;
private transient Player player;
private PacketContainer packet;
private boolean serverPacket;
private boolean cancel;
private AsyncMarker asyncMarker;
private boolean asynchronous;
/**
* Use the static constructors to create instances of this event.
* @param source - the event source.
*/
public PacketEvent(Object source) {
super(source);
}
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
super(source);
this.packet = packet;
this.player = player;
this.serverPacket = serverPacket;
}
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
super(origial.source);
this.packet = origial.packet;
this.player = origial.player;
this.cancel = origial.cancel;
this.serverPacket = origial.serverPacket;
this.asyncMarker = asyncMarker;
this.asynchronous = true;
}
/**
* Creates an event representing a client packet transmission.
* @param source - the event source.
* @param packet - the packet.
* @param client - the client that sent the packet.
* @return The event.
*/
public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) {
return new PacketEvent(source, packet, client, false);
}
/**
* Creates an event representing a server packet transmission.
* @param source - the event source.
* @param packet - the packet.
* @param recipient - the client that will receieve the packet.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
return new PacketEvent(source, packet, recipient, true);
}
/**
* Create an asynchronous packet event from a synchronous event and a async marker.
* @param event - the original synchronous event.
* @param marker - the asynchronous marker.
* @return The new packet event.
*/
public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) {
return new PacketEvent(event, marker);
}
/**
* Retrieves the packet that will be sent to the player.
* @return Packet to send to the player.
*/
public PacketContainer getPacket() {
return packet;
}
/**
* Replace the packet that will be sent to the player.
* @param packet - the packet that will be sent instead.
*/
public void setPacket(PacketContainer packet) {
this.packet = packet;
}
/**
* Retrieves the packet ID.
* @return The current packet ID.
*/
public int getPacketID() {
return packet.getID();
}
/**
* Retrieves whether or not the packet should be cancelled.
* @return TRUE if it should be cancelled, FALSE otherwise.
*/
public boolean isCancelled() {
return cancel;
}
/**
* Sets whether or not the packet should be cancelled.
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
*/
public void setCancelled(boolean cancel) {
this.cancel = cancel;
}
/**
* Retrieves the player that has sent the packet or is recieving it.
* @return The player associated with this event.
*/
public Player getPlayer() {
return player;
}
/**
* Whether or not this packet was created by the server.
* <p>
* Most listeners can deduce this by noting which listener method was invoked.
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
*/
public boolean isServerPacket() {
return serverPacket;
}
/**
* Retrieve the asynchronous marker.
* <p>
* If the packet is synchronous, this marker will be used to schedule an asynchronous event. In the following
* asynchronous event, the marker is used to correctly pass the packet around to the different threads.
* <p>
* Note that if there are no asynchronous events that can receive this packet, the marker is NULL.
* @return The current asynchronous marker, or NULL.
*/
public AsyncMarker getAsyncMarker() {
return asyncMarker;
}
/**
* Set the asynchronous marker.
* <p>
* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled
* to be processed asynchronously with the given settings.
* <p>
* Note that if there are no asynchronous events that can receive this packet, the marker should be NULL.
* @param asyncMarker - the new asynchronous marker, or NULL.
* @throws IllegalStateException If the current event is asynchronous.
*/
public void setAsyncMarker(AsyncMarker asyncMarker) {
if (isAsynchronous())
throw new IllegalStateException("The marker is immutable for asynchronous events");
this.asyncMarker = asyncMarker;
}
/**
* Determine if the packet event has been executed asynchronously or not.
* @return TRUE if this packet event is asynchronous, FALSE otherwise.
*/
public boolean isAsynchronous() {
return asynchronous;
}
private void writeObject(ObjectOutputStream output) throws IOException {
// Default serialization
output.defaultWriteObject();
// Write the name of the player (or NULL if it's not set)
output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null);
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
// Default deserialization
input.defaultReadObject();
final SerializedOfflinePlayer offlinePlayer = (SerializedOfflinePlayer) input.readObject();
if (offlinePlayer != null) {
// Better than nothing
player = offlinePlayer.getPlayer();
}
}
}
@@ -1,69 +1,69 @@
/*
* 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 org.bukkit.plugin.Plugin;
/**
* Represents a listener that recieves notifications when packets are sent or recieved.
* <p>
* Use {@link PacketAdapter} for a simple wrapper around this interface.
* @author Kristian
*/
public interface PacketListener {
/**
* Invoked right before a packet is transmitted from the server to the client.
* <p>
* Note that the packet may be replaced, if needed.
* <p>
* This method is executed on the main thread, and thus the Bukkit API is safe to use.
*
* @param event - the packet that should be sent.
*/
public void onPacketSending(PacketEvent event);
/**
* Invoked right before a recieved packet from a client is being processed.
* <p>
* <b>WARNING</b>: </br>
* This method will be called <i>asynchronously</i>! You should synchronize with the main
* thread using {@link org.bukkit.scheduler.BukkitScheduler#scheduleSyncDelayedTask(Plugin, Runnable, long) scheduleSyncDelayedTask}
* if you need to call the Bukkit API.
* @param event - the packet that has been recieved.
*/
public void onPacketReceiving(PacketEvent event);
/**
* Retrieve which packets sent by the server this listener will observe.
* @return List of server packets to observe, along with the priority.
*/
public ListeningWhitelist getSendingWhitelist();
/**
* Retrieve which packets sent by the client this listener will observe.
* @return List of server packets to observe, along with the priority.
*/
public ListeningWhitelist getReceivingWhitelist();
/**
* Retrieve the plugin that created list packet listener.
* @return The plugin, or NULL if not available.
*/
public Plugin getPlugin();
}
/*
* 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 org.bukkit.plugin.Plugin;
/**
* Represents a listener that recieves notifications when packets are sent or recieved.
* <p>
* Use {@link PacketAdapter} for a simple wrapper around this interface.
* @author Kristian
*/
public interface PacketListener {
/**
* Invoked right before a packet is transmitted from the server to the client.
* <p>
* Note that the packet may be replaced, if needed.
* <p>
* This method is executed on the main thread, and thus the Bukkit API is safe to use.
*
* @param event - the packet that should be sent.
*/
public void onPacketSending(PacketEvent event);
/**
* Invoked right before a recieved packet from a client is being processed.
* <p>
* <b>WARNING</b>: </br>
* This method will be called <i>asynchronously</i>! You should synchronize with the main
* thread using {@link org.bukkit.scheduler.BukkitScheduler#scheduleSyncDelayedTask(Plugin, Runnable, long) scheduleSyncDelayedTask}
* if you need to call the Bukkit API.
* @param event - the packet that has been recieved.
*/
public void onPacketReceiving(PacketEvent event);
/**
* Retrieve which packets sent by the server this listener will observe.
* @return List of server packets to observe, along with the priority.
*/
public ListeningWhitelist getSendingWhitelist();
/**
* Retrieve which packets sent by the client this listener will observe.
* @return List of server packets to observe, along with the priority.
*/
public ListeningWhitelist getReceivingWhitelist();
/**
* Retrieve the plugin that created list packet listener.
* @return The plugin, or NULL if not available.
*/
public Plugin getPlugin();
}
@@ -1,216 +1,233 @@
package com.comphenix.protocol.events;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.entity.Player;
/**
* Represents a player object that can be serialized by Java.
*
* @author Kristian
*/
class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = -2728976288470282810L;
private transient Location bedSpawnLocation;
// Relevant data about an offline player
private String name;
private long firstPlayed;
private long lastPlayed;
private boolean operator;
private boolean banned;
private boolean playedBefore;
private boolean online;
private boolean whitelisted;
// Proxy helper
private static Map<String, Method> lookup = new ConcurrentHashMap<String, Method>();
/**
* Constructor used by serialization.
*/
public SerializedOfflinePlayer() {
// Do nothing
}
/**
* Initialize this serializable offline player from another player.
* @param offline - another player.
*/
public SerializedOfflinePlayer(OfflinePlayer offline) {
this.name = offline.getName();
this.firstPlayed = offline.getFirstPlayed();
this.lastPlayed = offline.getLastPlayed();
this.operator = offline.isOp();
this.banned = offline.isBanned();
this.playedBefore = offline.hasPlayedBefore();
this.online = offline.isOnline();
this.whitelisted = offline.isWhitelisted();
}
@Override
public boolean isOp() {
return operator;
}
@Override
public void setOp(boolean operator) {
this.operator = operator;
}
@Override
public Map<String, Object> serialize() {
throw new UnsupportedOperationException();
}
@Override
public Location getBedSpawnLocation() {
return bedSpawnLocation;
}
@Override
public long getFirstPlayed() {
return firstPlayed;
}
@Override
public long getLastPlayed() {
return lastPlayed;
}
@Override
public String getName() {
return name;
}
@Override
public boolean hasPlayedBefore() {
return playedBefore;
}
@Override
public boolean isBanned() {
return banned;
}
@Override
public void setBanned(boolean banned) {
this.banned = banned;
}
@Override
public boolean isOnline() {
return online;
}
@Override
public boolean isWhitelisted() {
return whitelisted;
}
@Override
public void setWhitelisted(boolean whitelisted) {
this.whitelisted = whitelisted;
}
private void writeObject(ObjectOutputStream output) throws IOException {
output.defaultWriteObject();
// Serialize the bed spawn location
output.writeUTF(bedSpawnLocation.getWorld().getName());
output.writeDouble(bedSpawnLocation.getX());
output.writeDouble(bedSpawnLocation.getY());
output.writeDouble(bedSpawnLocation.getZ());
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
input.defaultReadObject();
// Well, this is a problem
bedSpawnLocation = new Location(
getWorld(input.readUTF()),
input.readDouble(),
input.readDouble(),
input.readDouble()
);
}
private World getWorld(String name) {
try {
// Try to get the world at least
return Bukkit.getServer().getWorld(name);
} catch (Exception e) {
// Screw it
return null;
}
}
@Override
public Player getPlayer() {
try {
// Try to get the real player underneath
return Bukkit.getServer().getPlayerExact(name);
} catch (Exception e) {
return getProxyPlayer();
}
}
/**
* Retrieve a player object that implements OfflinePlayer by refering to this object.
* <p>
* All other methods cause an exception.
* @return Proxy object.
*/
public Player getProxyPlayer() {
// Remember to initialize the method filter
if (lookup.size() == 0) {
// Add all public methods
for (Method method : OfflinePlayer.class.getMethods()) {
lookup.put(method.getName(), method);
}
}
// MORE CGLIB magic!
Enhancer ex = new Enhancer();
ex.setSuperclass(Player.class);
ex.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// There's no overloaded methods, so we don't care
Method offlineMethod = lookup.get(method.getName());
// Ignore all other methods
if (offlineMethod == null) {
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for offline players.");
}
// Invoke our on method
return offlineMethod.invoke(SerializedOfflinePlayer.this, args);
}
});
return (Player) ex.create();
}
}
/*
* 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.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.entity.Player;
/**
* Represents a player object that can be serialized by Java.
*
* @author Kristian
*/
class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = -2728976288470282810L;
private transient Location bedSpawnLocation;
// Relevant data about an offline player
private String name;
private long firstPlayed;
private long lastPlayed;
private boolean operator;
private boolean banned;
private boolean playedBefore;
private boolean online;
private boolean whitelisted;
// Proxy helper
private static Map<String, Method> lookup = new ConcurrentHashMap<String, Method>();
/**
* Constructor used by serialization.
*/
public SerializedOfflinePlayer() {
// Do nothing
}
/**
* Initialize this serializable offline player from another player.
* @param offline - another player.
*/
public SerializedOfflinePlayer(OfflinePlayer offline) {
this.name = offline.getName();
this.firstPlayed = offline.getFirstPlayed();
this.lastPlayed = offline.getLastPlayed();
this.operator = offline.isOp();
this.banned = offline.isBanned();
this.playedBefore = offline.hasPlayedBefore();
this.online = offline.isOnline();
this.whitelisted = offline.isWhitelisted();
}
@Override
public boolean isOp() {
return operator;
}
@Override
public void setOp(boolean operator) {
this.operator = operator;
}
@Override
public Map<String, Object> serialize() {
throw new UnsupportedOperationException();
}
@Override
public Location getBedSpawnLocation() {
return bedSpawnLocation;
}
@Override
public long getFirstPlayed() {
return firstPlayed;
}
@Override
public long getLastPlayed() {
return lastPlayed;
}
@Override
public String getName() {
return name;
}
@Override
public boolean hasPlayedBefore() {
return playedBefore;
}
@Override
public boolean isBanned() {
return banned;
}
@Override
public void setBanned(boolean banned) {
this.banned = banned;
}
@Override
public boolean isOnline() {
return online;
}
@Override
public boolean isWhitelisted() {
return whitelisted;
}
@Override
public void setWhitelisted(boolean whitelisted) {
this.whitelisted = whitelisted;
}
private void writeObject(ObjectOutputStream output) throws IOException {
output.defaultWriteObject();
// Serialize the bed spawn location
output.writeUTF(bedSpawnLocation.getWorld().getName());
output.writeDouble(bedSpawnLocation.getX());
output.writeDouble(bedSpawnLocation.getY());
output.writeDouble(bedSpawnLocation.getZ());
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
input.defaultReadObject();
// Well, this is a problem
bedSpawnLocation = new Location(
getWorld(input.readUTF()),
input.readDouble(),
input.readDouble(),
input.readDouble()
);
}
private World getWorld(String name) {
try {
// Try to get the world at least
return Bukkit.getServer().getWorld(name);
} catch (Exception e) {
// Screw it
return null;
}
}
@Override
public Player getPlayer() {
try {
// Try to get the real player underneath
return Bukkit.getServer().getPlayerExact(name);
} catch (Exception e) {
return getProxyPlayer();
}
}
/**
* Retrieve a player object that implements OfflinePlayer by refering to this object.
* <p>
* All other methods cause an exception.
* @return Proxy object.
*/
public Player getProxyPlayer() {
// Remember to initialize the method filter
if (lookup.size() == 0) {
// Add all public methods
for (Method method : OfflinePlayer.class.getMethods()) {
lookup.put(method.getName(), method);
}
}
// MORE CGLIB magic!
Enhancer ex = new Enhancer();
ex.setSuperclass(Player.class);
ex.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// There's no overloaded methods, so we don't care
Method offlineMethod = lookup.get(method.getName());
// Ignore all other methods
if (offlineMethod == null) {
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for offline players.");
}
// Invoke our on method
return offlineMethod.invoke(SerializedOfflinePlayer.this, args);
}
});
return (Player) ex.create();
}
}
@@ -0,0 +1,115 @@
/*
* 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 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.injector.PacketConstructor.Unwrapper;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
/**
* Represents an object capable of converting wrapped Bukkit objects into NMS objects.
* <p>
* Typical conversions include:
* <ul>
* <li>org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer</li>
* <li>org.bukkit.World -> net.minecraft.server.WorldServer</li>
* </ul>
*
* @author Kristian
*/
public class BukkitUnwrapper implements Unwrapper {
private static Map<Class<?>, Method> cache = new ConcurrentHashMap<Class<?>, Method>();
@SuppressWarnings("unchecked")
@Override
public Object unwrapItem(Object wrappedObject) {
// Special cases
if (wrappedObject == null) {
return null;
} else if (wrappedObject instanceof Collection) {
return handleCollection((Collection<Object>) wrappedObject);
}
Class<?> currentClass = wrappedObject.getClass();
Method cachedMethod = initializeCache(currentClass);
try {
// Retrieve the handle
if (cachedMethod != null)
return cachedMethod.invoke(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);
}
}
private Object handleCollection(Collection<Object> wrappedObject) {
@SuppressWarnings("unchecked")
Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
if (copy != null) {
// Unwrap every element
for (Object element : wrappedObject) {
copy.add(unwrapItem(element));
}
return copy;
} else {
// Impossible
return null;
}
}
private Method initializeCache(Class<?> type) {
// See if we're already determined this
if (cache.containsKey(type)) {
// We will never remove from the cache, so this ought to be thread safe
return cache.get(type);
}
try {
Method find = type.getMethod("getHandle");
// It's thread safe, as getMethod should return the same handle
cache.put(type, find);
return find;
} catch (SecurityException e) {
return null;
} catch (NoSuchMethodException e) {
return null;
}
}
}
@@ -0,0 +1,132 @@
package com.comphenix.protocol.injector;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
/**
* Represents a single delayed task.
*
* @author Kristian
*/
public class DelayedSingleTask {
protected int taskID = -1;
protected Plugin plugin;
protected BukkitScheduler scheduler;
protected boolean closed;
/**
* Create a single task scheduler.
* @param plugin - owner plugin.
*/
public DelayedSingleTask(Plugin plugin) {
this.plugin = plugin;
this.scheduler = plugin.getServer().getScheduler();
}
/**
* Create a single task scheduler.
* @param plugin - owner plugin.
* @param scheduler - specialized scheduler.
*/
public DelayedSingleTask(Plugin plugin, BukkitScheduler scheduler) {
this.plugin = plugin;
this.scheduler = scheduler;
}
/**
* Schedule a single task for execution.
* <p>
* Any previously scheduled task will be automatically cancelled.
* <p>
* Note that a tick delay of zero will execute the task immediately.
*
* @param ticksDelay - number of ticks before the task is executed.
* @param task - the task to schedule.
* @return TRUE if the task was successfully scheduled or executed, FALSE otherwise.
*/
public boolean schedule(long ticksDelay, Runnable task) {
if (ticksDelay < 0)
throw new IllegalArgumentException("Tick delay cannot be negative.");
if (task == null)
throw new IllegalArgumentException("task cannot be NULL");
if (closed)
return false;
// Special case
if (ticksDelay == 0) {
task.run();
return true;
}
// Boilerplate, boilerplate
final Runnable dispatch = task;
// Don't run multiple tasks!
cancel();
taskID = scheduler.scheduleSyncDelayedTask(plugin, new Runnable() {
@Override
public void run() {
dispatch.run();
taskID = -1;
}
}, ticksDelay);
return isRunning();
}
/**
* Whether or not a future task is scheduled to be executed.
* @return TRUE if a current task has been scheduled for execution, FALSE otherwise.
*/
public boolean isRunning() {
return taskID >= 0;
}
/**
* Cancel a future task from being executed.
* @return TRUE if a task was cancelled, FALSE otherwise.
*/
public boolean cancel() {
if (isRunning()) {
scheduler.cancelTask(taskID);
taskID = -1;
return true;
} else {
return false;
}
}
/**
* Retrieve the raw task ID.
* @return Raw task ID, or negative one if no task has been scheduled.
*/
public int getTaskID() {
return taskID;
}
/**
* Retrieve the plugin this task belongs to.
* @return The plugin scheduling the current taks.
*/
public Plugin getPlugin() {
return plugin;
}
/**
* Stop the current task and all future tasks scheduled by this instance.
*/
public synchronized void close() {
if (!closed) {
cancel();
plugin = null;
scheduler = null;
closed = true;
}
}
@Override
protected void finalize() throws Throwable {
close();
}
}
@@ -1,190 +1,206 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import com.comphenix.protocol.injector.PacketConstructor.BukkitUnwrapper;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.collect.Lists;
/**
* Used to perform certain operations on entities.
*
* @author Kristian
*/
class EntityUtilities {
private static Field entityTrackerField;
private static Field trackedEntitiesField;
private static Field trackedPlayersField;
private static Method hashGetMethod;
private static Method scanPlayersMethod;
/*
* While this function may look pretty bad, it's essentially just a reflection-warped
* version of the following:
*
* @SuppressWarnings("unchecked")
* public static void updateEntity2(Entity entity, List<Player> observers) {
*
* World world = entity.getWorld();
* WorldServer worldServer = ((CraftWorld) world).getHandle();
*
* EntityTracker tracker = worldServer.tracker;
* EntityTrackerEntry entry = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
*
* List<EntityPlayer> nmsPlayers = getNmsPlayers(observers);
*
* entry.trackedPlayers.removeAll(nmsPlayers);
* entry.scanPlayers(nmsPlayers);
* }
*
* private static List<EntityPlayer> getNmsPlayers(List<Player> players) {
* List<EntityPlayer> nsmPlayers = new ArrayList<EntityPlayer>();
*
* for (Player bukkitPlayer : players) {
* CraftPlayer craftPlayer = (CraftPlayer) bukkitPlayer;
* nsmPlayers.add(craftPlayer.getHandle());
* }
*
* return nsmPlayers;
* }
*
*/
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
World world = entity.getWorld();
Object worldServer = ((CraftWorld) world).getHandle();
// We have to rely on the class naming here.
if (entityTrackerField == null)
entityTrackerField = FuzzyReflection.fromObject(worldServer).getFieldByType(".*Tracker");
// Get the tracker
Object tracker = null;
try {
tracker = FieldUtils.readField(entityTrackerField, worldServer, false);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access 'tracker' field due to security limitations.", e);
}
if (trackedEntitiesField == null) {
@SuppressWarnings("rawtypes")
Set<Class> ignoredTypes = new HashSet<Class>();
// Well, this is more difficult. But we're looking for a Minecraft object that is not
// created by the constructor(s).
for (Constructor<?> constructor : tracker.getClass().getConstructors()) {
for (Class<?> type : constructor.getParameterTypes()) {
ignoredTypes.add(type);
}
}
// The Minecraft field that's NOT filled in by the constructor
trackedEntitiesField = FuzzyReflection.fromObject(tracker, true).
getFieldByType(FuzzyReflection.MINECRAFT_OBJECT, ignoredTypes);
}
// Read the entity hashmap
Object trackedEntities = null;
try {
trackedEntities = FieldUtils.readField(trackedEntitiesField, tracker, true);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access 'trackedEntities' field due to security limitations.", e);
}
// Getting the "get" method is pretty hard, but first - try to just get it by name
if (hashGetMethod == null) {
Class<?> type = trackedEntities.getClass();
try {
hashGetMethod = type.getMethod("get", int.class);
} catch (NoSuchMethodException e) {
Class<?>[] params = { int.class };
// Then it's probably the lowest named method that takes an int-parameter
for (Method method : type.getMethods()) {
if (Arrays.equals(params, method.getParameterTypes())) {
if (hashGetMethod == null ||
method.getName().compareTo(hashGetMethod.getName()) < 0) {
hashGetMethod = method;
}
}
}
}
}
try {
//EntityTrackerEntry trackEntity = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
Object trackerEntry = hashGetMethod.invoke(trackedEntities, entity.getEntityId());
if (trackedPlayersField == null) {
// This one is fairly easy
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
}
// Phew, finally there.
Collection<?> trackedPlayers = (Collection<?>) FieldUtils.readField(trackedPlayersField, trackerEntry, false);
List<Object> nmsPlayers = unwrapBukkit(observers);
// trackEntity.trackedPlayers.clear();
trackedPlayers.removeAll(nmsPlayers);
// We have to rely on a NAME once again. Damn it.
if (scanPlayersMethod == null) {
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
}
//trackEntity.scanPlayers(server.players);
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
} catch (IllegalArgumentException e) {
throw e;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occurred in Minecraft.", e);
} catch (SecurityException e) {
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
} catch (NoSuchMethodException e) {
throw new FieldAccessException("Canot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
}
}
private static List<Object> unwrapBukkit(List<Player> players) {
List<Object> output = Lists.newArrayList();
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
// Get the NMS equivalent
for (Player player : players) {
Object result = unwrapper.unwrapItem(player);
if (result != null)
output.add(result);
else
throw new IllegalArgumentException("Cannot unwrap item " + player);
}
return output;
}
}
/*
* 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 java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.collect.Lists;
/**
* Used to perform certain operations on entities.
*
* @author Kristian
*/
class EntityUtilities {
private static Field entityTrackerField;
private static Field trackedEntitiesField;
private static Field trackedPlayersField;
private static Method hashGetMethod;
private static Method scanPlayersMethod;
/*
* While this function may look pretty bad, it's essentially just a reflection-warped
* version of the following:
*
* @SuppressWarnings("unchecked")
* public static void updateEntity2(Entity entity, List<Player> observers) {
*
* World world = entity.getWorld();
* WorldServer worldServer = ((CraftWorld) world).getHandle();
*
* EntityTracker tracker = worldServer.tracker;
* EntityTrackerEntry entry = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
*
* List<EntityPlayer> nmsPlayers = getNmsPlayers(observers);
*
* entry.trackedPlayers.removeAll(nmsPlayers);
* entry.scanPlayers(nmsPlayers);
* }
*
* private static List<EntityPlayer> getNmsPlayers(List<Player> players) {
* List<EntityPlayer> nsmPlayers = new ArrayList<EntityPlayer>();
*
* for (Player bukkitPlayer : players) {
* CraftPlayer craftPlayer = (CraftPlayer) bukkitPlayer;
* nsmPlayers.add(craftPlayer.getHandle());
* }
*
* return nsmPlayers;
* }
*
*/
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
World world = entity.getWorld();
Object worldServer = ((CraftWorld) world).getHandle();
// We have to rely on the class naming here.
if (entityTrackerField == null)
entityTrackerField = FuzzyReflection.fromObject(worldServer).getFieldByType(".*Tracker");
// Get the tracker
Object tracker = null;
try {
tracker = FieldUtils.readField(entityTrackerField, worldServer, false);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access 'tracker' field due to security limitations.", e);
}
if (trackedEntitiesField == null) {
@SuppressWarnings("rawtypes")
Set<Class> ignoredTypes = new HashSet<Class>();
// Well, this is more difficult. But we're looking for a Minecraft object that is not
// created by the constructor(s).
for (Constructor<?> constructor : tracker.getClass().getConstructors()) {
for (Class<?> type : constructor.getParameterTypes()) {
ignoredTypes.add(type);
}
}
// The Minecraft field that's NOT filled in by the constructor
trackedEntitiesField = FuzzyReflection.fromObject(tracker, true).
getFieldByType(FuzzyReflection.MINECRAFT_OBJECT, ignoredTypes);
}
// Read the entity hashmap
Object trackedEntities = null;
try {
trackedEntities = FieldUtils.readField(trackedEntitiesField, tracker, true);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access 'trackedEntities' field due to security limitations.", e);
}
// Getting the "get" method is pretty hard, but first - try to just get it by name
if (hashGetMethod == null) {
Class<?> type = trackedEntities.getClass();
try {
hashGetMethod = type.getMethod("get", int.class);
} catch (NoSuchMethodException e) {
Class<?>[] params = { int.class };
// Then it's probably the lowest named method that takes an int-parameter
for (Method method : type.getMethods()) {
if (Arrays.equals(params, method.getParameterTypes())) {
if (hashGetMethod == null ||
method.getName().compareTo(hashGetMethod.getName()) < 0) {
hashGetMethod = method;
}
}
}
}
}
try {
//EntityTrackerEntry trackEntity = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
Object trackerEntry = hashGetMethod.invoke(trackedEntities, entity.getEntityId());
if (trackedPlayersField == null) {
// This one is fairly easy
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
}
// Phew, finally there.
Collection<?> trackedPlayers = (Collection<?>) FieldUtils.readField(trackedPlayersField, trackerEntry, false);
List<Object> nmsPlayers = unwrapBukkit(observers);
// trackEntity.trackedPlayers.clear();
trackedPlayers.removeAll(nmsPlayers);
// We have to rely on a NAME once again. Damn it.
if (scanPlayersMethod == null) {
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
}
//trackEntity.scanPlayers(server.players);
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
} catch (IllegalArgumentException e) {
throw e;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occurred in Minecraft.", e);
} catch (SecurityException e) {
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
} catch (NoSuchMethodException e) {
throw new FieldAccessException("Canot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
}
}
private static List<Object> unwrapBukkit(List<Player> players) {
List<Object> output = Lists.newArrayList();
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
// Get the NMS equivalent
for (Player player : players) {
Object result = unwrapper.unwrapItem(player);
if (result != null)
output.add(result);
else
throw new IllegalArgumentException("Cannot unwrap item " + player);
}
return output;
}
}
@@ -0,0 +1,39 @@
package com.comphenix.protocol.injector;
/**
* The current player phase. This is used to limit the number of different injections.
*
* @author Kristian
*/
public enum GamePhase {
/**
* Only listen for packets sent or received before a player has logged in.
*/
LOGIN,
/**
* Only listen for packets sent or received after a player has logged in.
*/
PLAYING,
/**
* Listen for every sent and received packet.
*/
BOTH;
/**
* Determine if the current value represents the login phase.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasLogin() {
return this == LOGIN || this == BOTH;
}
/**
* Determine if the current value represents the playing phase.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasPlaying() {
return this == PLAYING || this == BOTH;
}
}
@@ -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.injector;
import net.minecraft.server.Packet;
import com.comphenix.protocol.events.PacketEvent;
/**
* Represents an object that initiate the packet listeners.
*
* @author Kristian
*/
public interface ListenerInvoker {
/**
* Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke.
*/
public abstract void invokePacketRecieving(PacketEvent event);
/**
* Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke.
*/
public abstract void invokePacketSending(PacketEvent event);
/**
* Retrieve the associated ID of a packet.
* @param packet - the packet.
* @return The packet ID.
*/
public abstract int getPacketID(Packet packet);
}
@@ -1,104 +1,181 @@
/*
* 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 java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.server.Packet;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.base.Objects;
/**
* Static registries in Minecraft.
*
* @author Kristian
*/
@SuppressWarnings("rawtypes")
class MinecraftRegistry {
// The packet class to packet ID translator
private static Map<Class, Integer> packetToID;
// New proxy values
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
// Vanilla packets
private static Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
@SuppressWarnings({ "unchecked" })
public static Map<Class, Integer> getPacketToID() {
// Initialize it, if we haven't already
if (packetToID == null) {
try {
Field packetsField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType("packetsField", Map.class);
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
}
}
return packetToID;
}
public static Map<Integer, Class> getOverwrittenPackets() {
return overwrittenPackets;
}
public static Map<Integer, Class> getPreviousPackets() {
return previousValues;
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @return The associated class.
*/
public static Class getPacketClassFromID(int packetID) {
return getPacketClassFromID(packetID, false);
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @param vanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
*/
public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
// Optimized lookup
if (lookup.containsKey(packetID)) {
return lookup.get(packetID);
}
// Will most likely not be used
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) {
if (Objects.equal(entry.getValue(), packetID)) {
return entry.getKey();
}
}
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
}
}
/*
* 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 java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraft.server.Packet;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
/**
* Static registries in Minecraft.
*
* @author Kristian
*/
@SuppressWarnings("rawtypes")
class MinecraftRegistry {
// Fuzzy reflection
private static FuzzyReflection packetRegistry;
// The packet class to packet ID translator
private static Map<Class, Integer> packetToID;
// Whether or not certain packets are sent by the client or the server
private static Set<Integer> serverPackets;
private static Set<Integer> clientPackets;
// New proxy values
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
// Vanilla packets
private static Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
@SuppressWarnings({ "unchecked" })
public static Map<Class, Integer> getPacketToID() {
// Initialize it, if we haven't already
if (packetToID == null) {
try {
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
}
}
return packetToID;
}
/**
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
* @return Reflected packet registry.
*/
private static FuzzyReflection getPacketRegistry() {
if (packetRegistry == null)
packetRegistry = FuzzyReflection.fromClass(Packet.class, true);
return packetRegistry;
}
/**
* Retrieve the injected proxy classes handlig each packet ID.
* @return Injected classes.
*/
public static Map<Integer, Class> getOverwrittenPackets() {
return overwrittenPackets;
}
/**
* Retrieve the vanilla classes handling each packet ID.
* @return Vanilla classes.
*/
public static Map<Integer, Class> getPreviousPackets() {
return previousValues;
}
/**
* Retrieve every known and supported server packet.
* @return An immutable set of every known server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
public static Set<Integer> getServerPackets() throws FieldAccessException {
initializeSets();
return serverPackets;
}
/**
* Retrieve every known and supported client packet.
* @return An immutable set of every known client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
public static Set<Integer> getClientPackets() throws FieldAccessException {
initializeSets();
return clientPackets;
}
@SuppressWarnings("unchecked")
private static void initializeSets() throws FieldAccessException {
if (serverPackets == null || clientPackets == null) {
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
try {
if (sets.size() > 1) {
serverPackets = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
clientPackets = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
// Impossible
if (serverPackets == null || clientPackets == null)
throw new FieldAccessException("Packet sets are in an illegal state.");
// NEVER allow callers to modify the underlying sets
serverPackets = ImmutableSet.copyOf(serverPackets);
clientPackets = ImmutableSet.copyOf(clientPackets);
} else {
throw new FieldAccessException("Cannot retrieve packet client/server sets.");
}
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access field.", e);
}
}
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @return The associated class.
*/
public static Class getPacketClassFromID(int packetID) {
return getPacketClassFromID(packetID, false);
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @param vanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
*/
public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
// Optimized lookup
if (lookup.containsKey(packetID)) {
return lookup.get(packetID);
}
// Will most likely not be used
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) {
if (Objects.equal(entry.getValue(), packetID)) {
return entry.getKey();
}
}
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
}
}
@@ -1,231 +1,204 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.server.Packet;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
* A packet constructor that uses an internal Minecraft.
* @author Kristian
*
*/
public class PacketConstructor {
/**
* A packet constructor that automatically converts Bukkit types to their NMS conterpart.
* <p>
* Remember to call withPacket().
*/
public static PacketConstructor DEFAULT = new PacketConstructor(null);
// The constructor method that's actually responsible for creating the packet
private Constructor<?> constructorMethod;
// The packet ID
private int packetID;
// Used to unwrap Bukkit objects
private List<Unwrapper> unwrappers;
private PacketConstructor(Constructor<?> constructorMethod) {
this.constructorMethod = constructorMethod;
this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper());
}
private PacketConstructor(int packetID, Constructor<?> constructorMethod, List<Unwrapper> unwrappers) {
this.packetID = packetID;
this.constructorMethod = constructorMethod;
this.unwrappers = unwrappers;
}
public ImmutableList<Unwrapper> getUnwrappers() {
return ImmutableList.copyOf(unwrappers);
}
/**
* Retrieve the id of the packets this constructor creates.
* @return The ID of the packets this constructor will create.
*/
public int getPacketID() {
return packetID;
}
/**
* Return a copy of the current constructor with a different list of unwrappers.
* @param unwrappers - list of unwrappers that convert Bukkit wrappers into the equivalent NMS classes.
* @return A constructor with a different set of unwrappers.
*/
public PacketConstructor withUnwrappers(List<Unwrapper> unwrappers) {
return new PacketConstructor(packetID, constructorMethod, unwrappers);
}
/**
* Create a packet constructor that creates packets using the given types.
* @param id - packet ID.
* @param types - types to create.
* @return A packet constructor with these types.
* @throws IllegalArgumentException If no packet constructor could be created with these types.
*/
public PacketConstructor withPacket(int id, Object[] values) {
Class<?>[] types = new Class<?>[values.length];
for (int i = 0; i < types.length; i++) {
// Default type
if (values[i] != null) {
types[i] = values[i].getClass();
for (Unwrapper unwrapper : unwrappers) {
Object result = unwrapper.unwrapItem(values[i]);
// Update type we're searching for
if (result != null) {
types[i] = result.getClass();
break;
}
}
} else {
// Try it
types[i] = Object.class;
}
}
Class<?> packetType = MinecraftRegistry.getPacketClassFromID(id);
if (packetType == null)
throw new IllegalArgumentException("Could not find a packet by the id " + id);
// Find the correct constructor
for (Constructor<?> constructor : packetType.getConstructors()) {
Class<?>[] params = constructor.getParameterTypes();
if (isCompatible(types, params)) {
// Right, we've found our type
return new PacketConstructor(id, constructor, unwrappers);
}
}
throw new IllegalArgumentException("No suitable constructor could be found.");
}
/**
* Construct a packet using the special builtin Minecraft constructors.
* @param values - values containing Bukkit wrapped items to pass to Minecraft.
* @return The created packet.
* @throws FieldAccessException Failure due to a security limitation.
* @throws IllegalArgumentException Arguments doesn't match the constructor.
* @throws RuntimeException Minecraft threw an exception.
*/
public PacketContainer createPacket(Object... values) throws FieldAccessException {
try {
// Convert types
for (int i = 0; i < values.length; i++) {
for (Unwrapper unwrapper : unwrappers) {
Object converted = unwrapper.unwrapItem(values[i]);
if (converted != null) {
values[i] = converted;
break;
}
}
}
Packet nmsPacket = (Packet) constructorMethod.newInstance(values);
return new PacketContainer(packetID, nmsPacket);
} catch (IllegalArgumentException e) {
throw e;
} catch (InstantiationException e) {
throw new FieldAccessException("Cannot construct an abstract packet.", e);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot construct packet due to a security limitation.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Minecraft error.", e);
}
}
// Determine if a method with the types 'params' can be called with 'types'
private static boolean isCompatible(Class<?>[] types, Class<?>[] params) {
// Determine if the types are similar
if (params.length == types.length) {
for (int i = 0; i < params.length; i++) {
if (!params[i].isAssignableFrom(types[i])) {
return false;
}
}
return true;
}
// Parameter count must match
return false;
}
public static class BukkitUnwrapper implements Unwrapper {
private static Map<Class<?>, Method> cache = new ConcurrentHashMap<Class<?>, Method>();
@Override
public Object unwrapItem(Object wrappedObject) {
Class<?> currentClass = wrappedObject.getClass();
Method cachedMethod = initializeCache(currentClass);
try {
// Retrieve the handle
if (cachedMethod != null)
return cachedMethod.invoke(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);
}
}
private Method initializeCache(Class<?> type) {
// See if we're already determined this
if (cache.containsKey(type)) {
// We will never remove from the cache, so this ought to be thread safe
return cache.get(type);
}
try {
Method find = type.getMethod("getHandle");
// It's thread safe, as getMethod should return the same handle
cache.put(type, find);
return find;
} catch (SecurityException e) {
return null;
} catch (NoSuchMethodException e) {
return null;
}
}
}
public static interface Unwrapper {
public Object unwrapItem(Object wrappedObject);
}
}
/*
* 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 java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import net.minecraft.server.Packet;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
* A packet constructor that uses an internal Minecraft.
* @author Kristian
*
*/
public class PacketConstructor {
/**
* A packet constructor that automatically converts Bukkit types to their NMS conterpart.
* <p>
* Remember to call withPacket().
*/
public static final PacketConstructor DEFAULT = new PacketConstructor(null);
// The constructor method that's actually responsible for creating the packet
private Constructor<?> constructorMethod;
// The packet ID
private int packetID;
// Used to unwrap Bukkit objects
private List<Unwrapper> unwrappers;
private PacketConstructor(Constructor<?> constructorMethod) {
this.constructorMethod = constructorMethod;
this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper());
}
private PacketConstructor(int packetID, Constructor<?> constructorMethod, List<Unwrapper> unwrappers) {
this.packetID = packetID;
this.constructorMethod = constructorMethod;
this.unwrappers = unwrappers;
}
public ImmutableList<Unwrapper> getUnwrappers() {
return ImmutableList.copyOf(unwrappers);
}
/**
* Retrieve the id of the packets this constructor creates.
* @return The ID of the packets this constructor will create.
*/
public int getPacketID() {
return packetID;
}
/**
* Return a copy of the current constructor with a different list of unwrappers.
* @param unwrappers - list of unwrappers that convert Bukkit wrappers into the equivalent NMS classes.
* @return A constructor with a different set of unwrappers.
*/
public PacketConstructor withUnwrappers(List<Unwrapper> unwrappers) {
return new PacketConstructor(packetID, constructorMethod, unwrappers);
}
/**
* Create a packet constructor that creates packets using the given types.
* @param id - packet ID.
* @param values - types to create.
* @return A packet constructor with these types.
* @throws IllegalArgumentException If no packet constructor could be created with these types.
*/
public PacketConstructor withPacket(int id, Object[] values) {
Class<?>[] types = new Class<?>[values.length];
for (int i = 0; i < types.length; i++) {
// Default type
if (values[i] != null) {
types[i] = values[i].getClass();
for (Unwrapper unwrapper : unwrappers) {
Object result = unwrapper.unwrapItem(values[i]);
// Update type we're searching for
if (result != null) {
types[i] = result.getClass();
break;
}
}
} else {
// Try it
types[i] = Object.class;
}
}
Class<?> packetType = MinecraftRegistry.getPacketClassFromID(id);
if (packetType == null)
throw new IllegalArgumentException("Could not find a packet by the id " + id);
// Find the correct constructor
for (Constructor<?> constructor : packetType.getConstructors()) {
Class<?>[] params = constructor.getParameterTypes();
if (isCompatible(types, params)) {
// Right, we've found our type
return new PacketConstructor(id, constructor, unwrappers);
}
}
throw new IllegalArgumentException("No suitable constructor could be found.");
}
/**
* Construct a packet using the special builtin Minecraft constructors.
* @param values - values containing Bukkit wrapped items to pass to Minecraft.
* @return The created packet.
* @throws FieldAccessException Failure due to a security limitation.
* @throws IllegalArgumentException Arguments doesn't match the constructor.
* @throws RuntimeException Minecraft threw an exception.
*/
public PacketContainer createPacket(Object... values) throws FieldAccessException {
try {
// Convert types
for (int i = 0; i < values.length; i++) {
for (Unwrapper unwrapper : unwrappers) {
Object converted = unwrapper.unwrapItem(values[i]);
if (converted != null) {
values[i] = converted;
break;
}
}
}
Packet nmsPacket = (Packet) constructorMethod.newInstance(values);
return new PacketContainer(packetID, nmsPacket);
} catch (IllegalArgumentException e) {
throw e;
} catch (InstantiationException e) {
throw new FieldAccessException("Cannot construct an abstract packet.", e);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot construct packet due to a security limitation.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Minecraft error.", e);
}
}
// Determine if a method with the types 'params' can be called with 'types'
private static boolean isCompatible(Class<?>[] types, Class<?>[] params) {
// Determine if the types are similar
if (params.length == types.length) {
for (int i = 0; i < params.length; i++) {
if (!params[i].isAssignableFrom(types[i])) {
return false;
}
}
return true;
}
// Parameter count must match
return false;
}
/**
* Represents a unwrapper for a constructor parameter.
*
* @author Kristian
*/
public static interface Unwrapper {
/**
* Convert the given wrapped object to the equivalent net.minecraft.server object.
* @param wrappedObject - wrapped object.
* @return The net.minecraft.server object.
*/
public Object unwrapItem(Object wrappedObject);
}
}
@@ -1,229 +1,245 @@
/*
* 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 java.io.DataInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Player;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
/**
* This class is responsible for adding or removing proxy objects that intercepts recieved packets.
*
* @author Kristian
*/
class PacketInjector {
// The "put" method that associates a packet ID with a packet class
private static Method putMethod;
private static Object intHashMap;
// The packet filter manager
private ListenerInvoker manager;
// Allows us to determine the sender
private PlayerInjectionHandler playerInjection;
// Allows us to look up read packet injectors
private Map<Integer, ReadPacketModifier> readModifier;
// Class loader
private ClassLoader classLoader;
public PacketInjector(ClassLoader classLoader, ListenerInvoker manager,
PlayerInjectionHandler playerInjection) throws IllegalAccessException {
this.classLoader = classLoader;
this.manager = manager;
this.playerInjection = playerInjection;
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
initialize();
}
/**
* Undo a packet cancel.
* @param id - the id of the packet.
* @param packet - packet to uncancel.
*/
public void undoCancel(Integer id, Packet packet) {
ReadPacketModifier modifier = readModifier.get(id);
// Cancelled packets are represented with NULL
if (modifier != null && modifier.getOverride(packet) == null) {
modifier.removeOverride(packet);
}
}
private void initialize() throws IllegalAccessException {
if (intHashMap == null) {
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
Field intHashMapField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
try {
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Minecraft is incompatible.", e);
}
// Now, get the "put" method.
putMethod = FuzzyReflection.fromObject(intHashMap).getMethodByParameters("put", int.class, Object.class);
}
}
@SuppressWarnings("rawtypes")
public boolean addPacketHandler(int packetID) {
if (hasPacketHandler(packetID))
return false;
Enhancer ex = new Enhancer();
// Unfortunately, we can't easily distinguish between these two functions:
// * Object lookup(int par1)
// * Object removeObject(int par1)
// So, we'll use the classMapToInt registry instead.
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
Map<Class, Integer> registry = MinecraftRegistry.getPacketToID();
Class old = MinecraftRegistry.getPacketClassFromID(packetID);
// Check for previous injections
if (!old.getName().startsWith("net.minecraft.")) {
throw new IllegalStateException("Packet " + packetID + " has already been injected.");
}
// Subclass the specific packet class
ex.setSuperclass(old);
ex.setCallbackType(ReadPacketModifier.class);
ex.setUseCache(false);
ex.setClassLoader(classLoader);
Class proxy = ex.createClass();
// Create the proxy handler
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this);
readModifier.put(packetID, modifier);
// Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier });
try {
// Override values
putMethod.invoke(intHashMap, packetID, proxy);
previous.put(packetID, old);
registry.put(proxy, packetID);
overwritten.put(packetID, proxy);
return true;
} catch (IllegalArgumentException e) {
throw new RuntimeException("Illegal argument.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access method.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
}
}
@SuppressWarnings("rawtypes")
public boolean removePacketHandler(int packetID) {
if (!hasPacketHandler(packetID))
return false;
Map<Class, Integer> registry = MinecraftRegistry.getPacketToID();
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets();
// Use the old class definition
try {
Class old = previous.get(packetID);
Class proxy = MinecraftRegistry.getPacketClassFromID(packetID);
putMethod.invoke(intHashMap, packetID, old);
previous.remove(packetID);
readModifier.remove(packetID);
registry.remove(proxy);
overwritten.remove(packetID);
return true;
// Handle some problems
} catch (IllegalArgumentException e) {
return false;
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access method.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
}
}
public boolean hasPacketHandler(int packetID) {
return MinecraftRegistry.getPreviousPackets().containsKey(packetID);
}
public Set<Integer> getPacketHandlers() {
return MinecraftRegistry.getPreviousPackets().keySet();
}
// Called from the ReadPacketModified monitor
PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
Player client = playerInjection.getPlayerByConnection(input);
return packetRecieved(packet, client);
}
/**
* Let the packet listeners process the given packet.
* @param packet - a packet to process.
* @param client - the client that sent the packet.
* @return The resulting packet event.
*/
public PacketEvent packetRecieved(PacketContainer packet, Player client) {
PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client);
manager.invokePacketRecieving(event);
return event;
}
@SuppressWarnings("rawtypes")
public void cleanupAll() {
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
// Remove every packet handler
for (Integer id : previous.keySet()) {
removePacketHandler(id);
}
overwritten.clear();
previous.clear();
}
}
/*
* 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 java.io.DataInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Player;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
/**
* This class is responsible for adding or removing proxy objects that intercepts recieved packets.
*
* @author Kristian
*/
class PacketInjector {
// The "put" method that associates a packet ID with a packet class
private static Method putMethod;
private static Object intHashMap;
// The packet filter manager
private ListenerInvoker manager;
// Allows us to determine the sender
private PlayerInjectionHandler playerInjection;
// Allows us to look up read packet injectors
private Map<Integer, ReadPacketModifier> readModifier;
// Class loader
private ClassLoader classLoader;
public PacketInjector(ClassLoader classLoader, ListenerInvoker manager,
PlayerInjectionHandler playerInjection) throws IllegalAccessException {
this.classLoader = classLoader;
this.manager = manager;
this.playerInjection = playerInjection;
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
initialize();
}
/**
* Undo a packet cancel.
* @param id - the id of the packet.
* @param packet - packet to uncancel.
*/
public void undoCancel(Integer id, Packet packet) {
ReadPacketModifier modifier = readModifier.get(id);
// Cancelled packets are represented with NULL
if (modifier != null && modifier.getOverride(packet) == null) {
modifier.removeOverride(packet);
}
}
private void initialize() throws IllegalAccessException {
if (intHashMap == null) {
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
Field intHashMapField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
try {
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Minecraft is incompatible.", e);
}
// Now, get the "put" method.
putMethod = FuzzyReflection.fromObject(intHashMap).getMethodByParameters("put", int.class, Object.class);
}
}
@SuppressWarnings("rawtypes")
public boolean addPacketHandler(int packetID) {
if (hasPacketHandler(packetID))
return false;
Enhancer ex = new Enhancer();
// Unfortunately, we can't easily distinguish between these two functions:
// * Object lookup(int par1)
// * Object removeObject(int par1)
// So, we'll use the classMapToInt registry instead.
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
Map<Class, Integer> registry = MinecraftRegistry.getPacketToID();
Class old = MinecraftRegistry.getPacketClassFromID(packetID);
// If this packet is not known
if (old == null) {
throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version.");
}
// Check for previous injections
if (!old.getName().startsWith("net.minecraft.")) {
throw new IllegalStateException("Packet " + packetID + " has already been injected.");
}
// Subclass the specific packet class
ex.setSuperclass(old);
ex.setCallbackType(ReadPacketModifier.class);
ex.setUseCache(false);
ex.setClassLoader(classLoader);
Class proxy = ex.createClass();
// Create the proxy handler
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this);
readModifier.put(packetID, modifier);
// Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier });
try {
// Override values
putMethod.invoke(intHashMap, packetID, proxy);
previous.put(packetID, old);
registry.put(proxy, packetID);
overwritten.put(packetID, proxy);
return true;
} catch (IllegalArgumentException e) {
throw new RuntimeException("Illegal argument.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access method.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
}
}
@SuppressWarnings("rawtypes")
public boolean removePacketHandler(int packetID) {
if (!hasPacketHandler(packetID))
return false;
Map<Class, Integer> registry = MinecraftRegistry.getPacketToID();
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets();
// Use the old class definition
try {
Class old = previous.get(packetID);
Class proxy = MinecraftRegistry.getPacketClassFromID(packetID);
putMethod.invoke(intHashMap, packetID, old);
previous.remove(packetID);
readModifier.remove(packetID);
registry.remove(proxy);
overwritten.remove(packetID);
return true;
// Handle some problems
} catch (IllegalArgumentException e) {
return false;
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access method.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occured in IntHashMap.put.", e);
}
}
public boolean hasPacketHandler(int packetID) {
return MinecraftRegistry.getPreviousPackets().containsKey(packetID);
}
public Set<Integer> getPacketHandlers() {
return MinecraftRegistry.getPreviousPackets().keySet();
}
// Called from the ReadPacketModified monitor
PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
Player client = playerInjection.getPlayerByConnection(input);
// Never invoke a event if we don't know where it's from
if (client != null)
return packetRecieved(packet, client);
else
return null;
}
/**
* Let the packet listeners process the given packet.
* @param packet - a packet to process.
* @param client - the client that sent the packet.
* @return The resulting packet event.
*/
public PacketEvent packetRecieved(PacketContainer packet, Player client) {
PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client);
manager.invokePacketRecieving(event);
return event;
}
@SuppressWarnings("rawtypes")
public synchronized void cleanupAll() {
Map<Integer, Class> overwritten = MinecraftRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = MinecraftRegistry.getPreviousPackets();
// Remove every packet handler
for (Integer id : previous.keySet().toArray(new Integer[0])) {
removePacketHandler(id);
}
overwritten.clear();
previous.clear();
}
/**
* Inform the current PlayerInjector that it should update the DataInputStream next.
* @param player - the player to update.
*/
public void scheduleDataInputRefresh(Player player) {
playerInjection.scheduleDataInputRefresh(player);
}
}
@@ -1,41 +1,58 @@
package com.comphenix.protocol.injector;
/**
* Invoked when attempting to use a player that has already logged out.
*
* @author Kristian
*/
public class PlayerLoggedOutException extends RuntimeException {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 4889257862160145234L;
public PlayerLoggedOutException() {
// Default error message
super("Cannot inject a player that has already logged out.");
}
public PlayerLoggedOutException(String message, Throwable cause) {
super(message, cause);
}
public PlayerLoggedOutException(String message) {
super(message);
}
public PlayerLoggedOutException(Throwable cause) {
super(cause);
}
/**
* Construct an exception from a formatted message.
* @param message - the message to format.
* @param params - parameters.
* @return The formated exception
*/
public static PlayerLoggedOutException fromFormat(String message, Object... params) {
return new PlayerLoggedOutException(String.format(message, params));
}
}
/*
* 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;
/**
* Invoked when attempting to use a player that has already logged out.
*
* @author Kristian
*/
public class PlayerLoggedOutException extends RuntimeException {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 4889257862160145234L;
public PlayerLoggedOutException() {
// Default error message
super("Cannot inject a player that has already logged out.");
}
public PlayerLoggedOutException(String message, Throwable cause) {
super(message, cause);
}
public PlayerLoggedOutException(String message) {
super(message);
}
public PlayerLoggedOutException(Throwable cause) {
super(cause);
}
/**
* Construct an exception from a formatted message.
* @param message - the message to format.
* @param params - parameters.
* @return The formated exception
*/
public static PlayerLoggedOutException fromFormat(String message, Object... params) {
return new PlayerLoggedOutException(String.format(message, params));
}
}
@@ -1,64 +1,81 @@
package com.comphenix.protocol.injector;
import com.comphenix.protocol.events.ListenerPriority;
import com.google.common.base.Objects;
import com.google.common.primitives.Ints;
/**
* Represents a listener with a priority.
*
* @author Kristian
*/
public class PrioritizedListener<TListener> implements Comparable<PrioritizedListener<TListener>> {
private TListener listener;
private ListenerPriority priority;
public PrioritizedListener(TListener listener, ListenerPriority priority) {
this.listener = listener;
this.priority = priority;
}
@Override
public int compareTo(PrioritizedListener<TListener> other) {
// This ensures that lower priority listeners are executed first
return Ints.compare(
this.getPriority().getSlot(),
other.getPriority().getSlot());
}
// Note that this equals() method is NOT consistent with compareTo().
// But, it's a private class so who cares.
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
// We only care about the listener - priority itself should not make a difference
if(obj instanceof PrioritizedListener){
final PrioritizedListener<TListener> other = (PrioritizedListener<TListener>) obj;
return Objects.equal(listener, other.listener);
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hashCode(listener);
}
/**
* Retrieve the underlying listener.
* @return Underlying listener.
*/
public TListener getListener() {
return listener;
}
/**
* Retrieve the priority of this listener.
* @return Listener priority.
*/
public ListenerPriority getPriority() {
return priority;
}
}
/*
* 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 com.comphenix.protocol.events.ListenerPriority;
import com.google.common.base.Objects;
import com.google.common.primitives.Ints;
/**
* Represents a listener with a priority.
*
* @author Kristian
*/
public class PrioritizedListener<TListener> implements Comparable<PrioritizedListener<TListener>> {
private TListener listener;
private ListenerPriority priority;
public PrioritizedListener(TListener listener, ListenerPriority priority) {
this.listener = listener;
this.priority = priority;
}
@Override
public int compareTo(PrioritizedListener<TListener> other) {
// This ensures that lower priority listeners are executed first
return Ints.compare(
this.getPriority().getSlot(),
other.getPriority().getSlot());
}
// Note that this equals() method is NOT consistent with compareTo().
// But, it's a private class so who cares.
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
// We only care about the listener - priority itself should not make a difference
if(obj instanceof PrioritizedListener){
final PrioritizedListener<TListener> other = (PrioritizedListener<TListener>) obj;
return Objects.equal(listener, other.listener);
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hashCode(listener);
}
/**
* Retrieve the underlying listener.
* @return Underlying listener.
*/
public TListener getListener() {
return listener;
}
/**
* Retrieve the priority of this listener.
* @return Listener priority.
*/
public ListenerPriority getPriority() {
return priority;
}
}
@@ -1,122 +1,129 @@
/*
* 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 java.io.DataInputStream;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.WeakHashMap;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
class ReadPacketModifier implements MethodInterceptor {
@SuppressWarnings("rawtypes")
private static Class[] parameters = { DataInputStream.class };
// Common for all packets of the same type
private PacketInjector packetInjector;
private int packetID;
// Whether or not a packet has been cancelled
private static WeakHashMap<Object, Object> override = new WeakHashMap<Object, Object>();
public ReadPacketModifier(int packetID, PacketInjector packetInjector) {
this.packetID = packetID;
this.packetInjector = packetInjector;
}
/**
* Remove any packet overrides.
* @param packet - the packet to rever
*/
public void removeOverride(Packet packet) {
override.remove(packet);
}
/**
* Retrieve the packet that overrides the methods of the given packet.
* @param packet - the given packet.
* @return Overriden object.
*/
public Object getOverride(Packet packet) {
return override.get(packet);
}
@Override
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object returnValue = null;
String methodName = method.getName();
// We always pass these down (otherwise, we'll end up with a infinite loop)
if (methodName.equals("hashCode") || methodName.equals("equals") || methodName.equals("toString")) {
return proxy.invokeSuper(thisObj, args);
}
if (override.containsKey(thisObj)) {
Object overridenObject = override.get(thisObj);
// This packet has been cancelled
if (overridenObject == null) {
// So, cancel all void methods
if (method.getReturnType().equals(Void.TYPE))
return null;
else // Revert to normal for everything else
overridenObject = thisObj;
}
returnValue = proxy.invokeSuper(overridenObject, args);
} else {
returnValue = proxy.invokeSuper(thisObj, args);
}
// Is this a readPacketData method?
if (returnValue == null &&
Arrays.equals(method.getParameterTypes(), parameters)) {
// We need this in order to get the correct player
DataInputStream input = (DataInputStream) args[0];
// Let the people know
PacketContainer container = new PacketContainer(packetID, (Packet) thisObj);
PacketEvent event = packetInjector.packetRecieved(container, input);
Packet result = event.getPacket().getHandle();
// Handle override
if (event != null) {
if (event.isCancelled()) {
override.put(thisObj, null);
} else if (!objectEquals(thisObj, result)) {
override.put(thisObj, result);
}
}
}
return returnValue;
}
private boolean objectEquals(Object a, Object b) {
return System.identityHashCode(a) != System.identityHashCode(b);
}
}
/*
* 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 java.io.DataInputStream;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.WeakHashMap;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
class ReadPacketModifier implements MethodInterceptor {
@SuppressWarnings("rawtypes")
private static Class[] parameters = { DataInputStream.class };
// Common for all packets of the same type
private PacketInjector packetInjector;
private int packetID;
// Whether or not a packet has been cancelled
private static WeakHashMap<Object, Object> override = new WeakHashMap<Object, Object>();
public ReadPacketModifier(int packetID, PacketInjector packetInjector) {
this.packetID = packetID;
this.packetInjector = packetInjector;
}
/**
* Remove any packet overrides.
* @param packet - the packet to rever
*/
public void removeOverride(Packet packet) {
override.remove(packet);
}
/**
* Retrieve the packet that overrides the methods of the given packet.
* @param packet - the given packet.
* @return Overriden object.
*/
public Object getOverride(Packet packet) {
return override.get(packet);
}
@Override
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object returnValue = null;
String methodName = method.getName();
// We always pass these down (otherwise, we'll end up with a infinite loop)
if (methodName.equals("hashCode") || methodName.equals("equals") || methodName.equals("toString")) {
return proxy.invokeSuper(thisObj, args);
}
if (override.containsKey(thisObj)) {
Object overridenObject = override.get(thisObj);
// This packet has been cancelled
if (overridenObject == null) {
// So, cancel all void methods
if (method.getReturnType().equals(Void.TYPE))
return null;
else // Revert to normal for everything else
overridenObject = thisObj;
}
returnValue = proxy.invokeSuper(overridenObject, args);
} else {
returnValue = proxy.invokeSuper(thisObj, args);
}
// Is this a readPacketData method?
if (returnValue == null &&
Arrays.equals(method.getParameterTypes(), parameters)) {
// We need this in order to get the correct player
DataInputStream input = (DataInputStream) args[0];
// Let the people know
PacketContainer container = new PacketContainer(packetID, (Packet) thisObj);
PacketEvent event = packetInjector.packetRecieved(container, input);
// Handle override
if (event != null) {
Packet result = event.getPacket().getHandle();
if (event.isCancelled()) {
override.put(thisObj, null);
} else if (!objectEquals(thisObj, result)) {
override.put(thisObj, result);
}
// Update DataInputStream next time
if (!event.isCancelled() && packetID == Packets.Server.KEY_RESPONSE) {
packetInjector.scheduleDataInputRefresh(event.getPlayer());
}
}
}
return returnValue;
}
private boolean objectEquals(Object a, Object b) {
return System.identityHashCode(a) != System.identityHashCode(b);
}
}
@@ -1,66 +1,83 @@
package com.comphenix.protocol.injector;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
/**
* Registry of synchronous packet listeners.
*
* @author Kristian
*/
class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<PacketListener> {
/**
* Invokes the given packet event for every registered listener.
* @param logger - the logger that will be used to inform about listener exceptions.
* @param event - the packet event to invoke.
*/
public void invokePacketRecieving(Logger logger, PacketEvent event) {
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
if (list == null)
return;
// The returned list is thread-safe
for (PrioritizedListener<PacketListener> element : list) {
try {
element.getListener().onPacketReceiving(event);
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
logger.log(Level.SEVERE,
"Exception occured in onPacketReceiving() for " +
PacketAdapter.getPluginName(element.getListener()), e);
}
}
}
/**
* Invokes the given packet event for every registered listener.
* @param logger - the logger that will be used to inform about listener exceptions.
* @param event - the packet event to invoke.
*/
public void invokePacketSending(Logger logger, PacketEvent event) {
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
if (list == null)
return;
for (PrioritizedListener<PacketListener> element : list) {
try {
element.getListener().onPacketSending(event);
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
logger.log(Level.SEVERE,
"Exception occured in onPacketSending() for " +
PacketAdapter.getPluginName(element.getListener()), e);
}
}
}
}
/*
* 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 java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
/**
* Registry of synchronous packet listeners.
*
* @author Kristian
*/
class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<PacketListener> {
/**
* Invokes the given packet event for every registered listener.
* @param logger - the logger that will be used to inform about listener exceptions.
* @param event - the packet event to invoke.
*/
public void invokePacketRecieving(Logger logger, PacketEvent event) {
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
if (list == null)
return;
// The returned list is thread-safe
for (PrioritizedListener<PacketListener> element : list) {
try {
element.getListener().onPacketReceiving(event);
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
logger.log(Level.SEVERE,
"Exception occured in onPacketReceiving() for " +
PacketAdapter.getPluginName(element.getListener()), e);
}
}
}
/**
* Invokes the given packet event for every registered listener.
* @param logger - the logger that will be used to inform about listener exceptions.
* @param event - the packet event to invoke.
*/
public void invokePacketSending(Logger logger, PacketEvent event) {
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
if (list == null)
return;
for (PrioritizedListener<PacketListener> element : list) {
try {
element.getListener().onPacketSending(event);
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
logger.log(Level.SEVERE,
"Exception occured in onPacketSending() for " +
PacketAdapter.getPluginName(element.getListener()), e);
}
}
}
}
@@ -1,113 +1,113 @@
/*
* 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 java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import net.minecraft.server.Packet;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.compiler.CompileListener;
import com.comphenix.protocol.reflect.compiler.CompiledStructureModifier;
/**
* Caches structure modifiers.
* @author Kristian
*/
public class StructureCache {
// Structure modifiers
private static ConcurrentMap<Integer, StructureModifier<Object>> structureModifiers =
new ConcurrentHashMap<Integer, StructureModifier<Object>>();
private static Set<Integer> compiling = new HashSet<Integer>();
/**
* Creates an empty Minecraft packet of the given ID.
* @param id - packet ID.
* @return Created packet.
*/
public static Packet newPacket(int id) {
try {
return (Packet) MinecraftRegistry.getPacketClassFromID(id, true).newInstance();
} catch (InstantiationException e) {
return null;
} catch (IllegalAccessException e) {
throw new RuntimeException("Access denied.", e);
}
}
/**
* Retrieve a cached structure modifier for the given packet id.
* @param id - packet ID.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(int id) {
// Compile structures by default
return getStructure(id, true);
}
/**
* Retrieve a cached structure modifier for the given packet id.
* @param id - packet ID.
* @param compile - whether or not to asynchronously compile the structure modifier.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(int id, boolean compile) {
StructureModifier<Object> result = structureModifiers.get(id);
// We don't want to create this for every lookup
if (result == null) {
// Use the vanilla class definition
final StructureModifier<Object> value = new StructureModifier<Object>(
MinecraftRegistry.getPacketClassFromID(id, true), Packet.class, true);
result = structureModifiers.putIfAbsent(id, value);
// We may end up creating multiple modifiers, but we'll agree on which to use
if (result == null) {
result = value;
}
}
// Automatically compile the structure modifier
if (compile && !(result instanceof CompiledStructureModifier)) {
// Compilation is many orders of magnitude slower than synchronization
synchronized (compiling) {
final int idCopy = id;
final BackgroundCompiler compiler = BackgroundCompiler.getInstance();
if (!compiling.contains(id) && compiler != null) {
compiler.scheduleCompilation(result, new CompileListener<Object>() {
@Override
public void onCompiled(StructureModifier<Object> compiledModifier) {
structureModifiers.put(idCopy, compiledModifier);
}
});
compiling.add(id);
}
}
}
return result;
}
}
/*
* 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 java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import net.minecraft.server.Packet;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.compiler.CompileListener;
import com.comphenix.protocol.reflect.compiler.CompiledStructureModifier;
/**
* Caches structure modifiers.
* @author Kristian
*/
public class StructureCache {
// Structure modifiers
private static ConcurrentMap<Integer, StructureModifier<Object>> structureModifiers =
new ConcurrentHashMap<Integer, StructureModifier<Object>>();
private static Set<Integer> compiling = new HashSet<Integer>();
/**
* Creates an empty Minecraft packet of the given ID.
* @param id - packet ID.
* @return Created packet.
*/
public static Packet newPacket(int id) {
try {
return (Packet) MinecraftRegistry.getPacketClassFromID(id, true).newInstance();
} catch (InstantiationException e) {
return null;
} catch (IllegalAccessException e) {
throw new RuntimeException("Access denied.", e);
}
}
/**
* Retrieve a cached structure modifier for the given packet id.
* @param id - packet ID.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(int id) {
// Compile structures by default
return getStructure(id, true);
}
/**
* Retrieve a cached structure modifier for the given packet id.
* @param id - packet ID.
* @param compile - whether or not to asynchronously compile the structure modifier.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(int id, boolean compile) {
StructureModifier<Object> result = structureModifiers.get(id);
// We don't want to create this for every lookup
if (result == null) {
// Use the vanilla class definition
final StructureModifier<Object> value = new StructureModifier<Object>(
MinecraftRegistry.getPacketClassFromID(id, true), Packet.class, true);
result = structureModifiers.putIfAbsent(id, value);
// We may end up creating multiple modifiers, but we'll agree on which to use
if (result == null) {
result = value;
}
}
// Automatically compile the structure modifier
if (compile && !(result instanceof CompiledStructureModifier)) {
// Compilation is many orders of magnitude slower than synchronization
synchronized (compiling) {
final int idCopy = id;
final BackgroundCompiler compiler = BackgroundCompiler.getInstance();
if (!compiling.contains(id) && compiler != null) {
compiler.scheduleCompilation(result, new CompileListener<Object>() {
@Override
public void onCompiled(StructureModifier<Object> compiledModifier) {
structureModifiers.put(idCopy, compiledModifier);
}
});
compiling.add(id);
}
}
}
return result;
}
}
@@ -1,115 +1,132 @@
package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Set;
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* The array list that notifies when packets are sent by the server.
*
* @author Kristian
*/
class InjectedArrayList extends ArrayList<Packet> {
/**
* Silly Eclipse.
*/
private static final long serialVersionUID = -1173865905404280990L;
private PlayerInjector injector;
private Set<Packet> ignoredPackets;
private ClassLoader classLoader;
public InjectedArrayList(ClassLoader classLoader, PlayerInjector injector, Set<Packet> ignoredPackets) {
this.classLoader = classLoader;
this.injector = injector;
this.ignoredPackets = ignoredPackets;
}
@Override
public boolean add(Packet packet) {
Packet result = null;
// Check for fake packets and ignored packets
if (packet instanceof FakePacket) {
return true;
} else if (ignoredPackets.contains(packet)) {
ignoredPackets.remove(packet);
} else {
result = injector.handlePacketRecieved(packet);
}
// A NULL packet indicate cancelling
try {
if (result != null) {
super.add(result);
} else {
// We'll use the FakePacket marker instead of preventing the filters
injector.sendServerPacket(createNegativePacket(packet), true);
}
// Collection.add contract
return true;
} catch (InvocationTargetException e) {
throw new RuntimeException("Reverting cancelled packet failed.", e.getTargetException());
}
}
/**
* Used by a hack that reverses the effect of a cancelled packet. Returns a packet
* whereby every int method's return value is inverted (a => -a).
*
* @param source - packet to invert.
* @return The inverted packet.
*/
Packet createNegativePacket(Packet source) {
Enhancer ex = new Enhancer();
Class<?> type = source.getClass();
// We want to subtract the byte amount that were added to the running
// total of outstanding packets. Otherwise, cancelling too many packets
// might cause a "disconnect.overflow" error.
//
// We do that by constructing a special packet of the same type that returns
// a negative integer for all zero-parameter integer methods. This includes the
// size() method, which is used by the queue method to count the number of
// bytes to add.
//
// Essentially, we have:
//
// public class NegativePacket extends [a packet] {
// @Override
// public int size() {
// return -super.size();
// }
// ect.
// }
ex.setInterfaces(new Class[] { FakePacket.class } );
ex.setUseCache(true);
ex.setClassLoader(classLoader);
ex.setSuperclass(type);
ex.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (method.getReturnType().equals(int.class) && args.length == 0) {
Integer result = (Integer) proxy.invokeSuper(obj, args);
return -result;
} else {
return proxy.invokeSuper(obj, args);
}
}
});
return (Packet) ex.create();
}
}
/*
* 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;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Set;
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* The array list that notifies when packets are sent by the server.
*
* @author Kristian
*/
class InjectedArrayList extends ArrayList<Packet> {
/**
* Silly Eclipse.
*/
private static final long serialVersionUID = -1173865905404280990L;
private PlayerInjector injector;
private Set<Packet> ignoredPackets;
private ClassLoader classLoader;
public InjectedArrayList(ClassLoader classLoader, PlayerInjector injector, Set<Packet> ignoredPackets) {
this.classLoader = classLoader;
this.injector = injector;
this.ignoredPackets = ignoredPackets;
}
@Override
public boolean add(Packet packet) {
Packet result = null;
// Check for fake packets and ignored packets
if (packet instanceof FakePacket) {
return true;
} else if (ignoredPackets.contains(packet)) {
ignoredPackets.remove(packet);
} else {
result = injector.handlePacketSending(packet);
}
// A NULL packet indicate cancelling
try {
if (result != null) {
super.add(result);
} else {
// We'll use the FakePacket marker instead of preventing the filters
injector.sendServerPacket(createNegativePacket(packet), true);
}
// Collection.add contract
return true;
} catch (InvocationTargetException e) {
throw new RuntimeException("Reverting cancelled packet failed.", e.getTargetException());
}
}
/**
* Used by a hack that reverses the effect of a cancelled packet. Returns a packet
* whereby every int method's return value is inverted (a => -a).
*
* @param source - packet to invert.
* @return The inverted packet.
*/
Packet createNegativePacket(Packet source) {
Enhancer ex = new Enhancer();
Class<?> type = source.getClass();
// We want to subtract the byte amount that were added to the running
// total of outstanding packets. Otherwise, cancelling too many packets
// might cause a "disconnect.overflow" error.
//
// We do that by constructing a special packet of the same type that returns
// a negative integer for all zero-parameter integer methods. This includes the
// size() method, which is used by the queue method to count the number of
// bytes to add.
//
// Essentially, we have:
//
// public class NegativePacket extends [a packet] {
// @Override
// public int size() {
// return -super.size();
// }
// ect.
// }
ex.setInterfaces(new Class[] { FakePacket.class } );
ex.setUseCache(true);
ex.setClassLoader(classLoader);
ex.setSuperclass(type);
ex.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (method.getReturnType().equals(int.class) && args.length == 0) {
Integer result = (Integer) proxy.invokeSuper(obj, args);
return -result;
} else {
return proxy.invokeSuper(obj, args);
}
}
});
return (Packet) ex.create();
}
}
@@ -1,207 +1,295 @@
package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Server;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectCloner;
import com.comphenix.protocol.reflect.VolatileField;
/**
* Used to ensure that the 1.3 server is referencing the correct server handler.
*
* @author Kristian
*/
class InjectedServerConnection {
private static Field listenerThreadField;
private static Field minecraftServerField;
private static Method serverConnectionMethod;
private static Field listField;
private List<VolatileField> listFields;
private List<ReplacedArrayList<Object>> replacedLists;
private Server server;
private Logger logger;
private boolean hasAttempted;
private boolean hasSuccess;
private Object minecraftServer = null;
public InjectedServerConnection(Logger logger, Server server) {
this.listFields = new ArrayList<VolatileField>();
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
this.logger = logger;
this.server = server;
}
public void injectList() {
// Only execute this method once
if (!hasAttempted)
hasAttempted = true;
else
return;
if (minecraftServerField == null)
minecraftServerField = FuzzyReflection.fromObject(server, true).getFieldByType(".*MinecraftServer");
try {
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
} catch (IllegalAccessException e1) {
logger.log(Level.WARNING, "Cannot extract minecraft server from Bukkit.");
return;
}
try {
if (serverConnectionMethod == null)
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
getMethodByParameters("getServerConnection", ".*ServerConnection", new String[] {});
// We're using Minecraft 1.3.1
injectServerConnection();
} catch (RuntimeException e) {
// Minecraft 1.2.5 or lower
injectListenerThread();
}
}
private void injectListenerThread() {
try {
if (listenerThreadField == null)
listenerThreadField = FuzzyReflection.fromClass(minecraftServerField.getType()).
getFieldByType(".*NetworkListenThread");
} catch (RuntimeException e) {
logger.log(Level.SEVERE, "Cannot find listener thread in MinecraftServer.");
return;
}
Object listenerThread = null;
// Attempt to get the thread
try {
listenerThread = listenerThreadField.get(minecraftServer);
} catch (Exception e) {
logger.log(Level.WARNING, "Unable to read the listener thread.");
return;
}
// Ok, great. Get every list field
List<Field> lists = FuzzyReflection.fromClass(listenerThreadField.getType()).getFieldListByType(List.class);
for (Field list : lists) {
injectIntoList(listenerThread, list);
}
hasSuccess = true;
}
private void injectServerConnection() {
Object serverConnection = null;
// Careful - we might fail
try {
serverConnection = serverConnectionMethod.invoke(minecraftServer);
} catch (Exception ex) {
logger.log(Level.WARNING, "Unable to retrieve server connection", ex);
return;
}
if (listField == null)
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
getFieldByType("serverConnection", List.class);
injectIntoList(serverConnection, listField);
hasSuccess = true;
}
@SuppressWarnings("unchecked")
private void injectIntoList(Object instance, Field field) {
VolatileField listFieldRef = new VolatileField(listField, instance, true);
List<Object> list = (List<Object>) listFieldRef.getValue();
// Careful not to inject twice
if (list instanceof ReplacedArrayList) {
replacedLists.add((ReplacedArrayList<Object>) list);
} else {
replacedLists.add(createReplacement(list));
listFieldRef.setValue(replacedLists.get(0));
listFields.add(listFieldRef);
}
}
// Hack to avoid the "moved to quickly" error
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
return new ReplacedArrayList<Object>(list) {
@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
ObjectCloner.copyTo(inserting, replacement, inserting.getClass());
}
}
};
}
/**
* Replace the server handler instance kept by the "keep alive" object.
* @param oldHandler - old server handler.
* @param newHandler - new, proxied server handler.
*/
public void replaceServerHandler(Object oldHandler, Object newHandler) {
if (!hasAttempted) {
injectList();
}
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.addMapping(oldHandler, newHandler);
}
}
}
/**
* Revert to the old vanilla server handler, if it has been replaced.
* @param oldHandler - old vanilla server handler.
*/
public void revertServerHandler(Object oldHandler) {
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.removeMapping(oldHandler);
}
}
}
/**
* Undoes everything.
*/
public void cleanupAll() {
if (replacedLists.size() > 0) {
// Repair the underlying lists
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.revertAll();
}
for (VolatileField field : listFields) {
field.revertValue();
}
listFields.clear();
replacedLists.clear();
}
}
}
/*
* 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.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.server.NetLoginHandler;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Server;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectCloner;
import com.comphenix.protocol.reflect.VolatileField;
/**
* Used to ensure that the 1.3 server is referencing the correct server handler.
*
* @author Kristian
*/
class InjectedServerConnection {
private static Field listenerThreadField;
private static Field minecraftServerField;
private static Field listField;
private static Field dedicatedThreadField;
private static Method serverConnectionMethod;
private List<VolatileField> listFields;
private List<ReplacedArrayList<Object>> replacedLists;
// Used to inject net handlers
private NetLoginInjector netLoginInjector;
private Server server;
private Logger logger;
private boolean hasAttempted;
private boolean hasSuccess;
private Object minecraftServer = null;
public InjectedServerConnection(Logger logger, Server server, NetLoginInjector netLoginInjector) {
this.listFields = new ArrayList<VolatileField>();
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
this.logger = logger;
this.server = server;
this.netLoginInjector = netLoginInjector;
}
public void injectList() {
// Only execute this method once
if (!hasAttempted)
hasAttempted = true;
else
return;
if (minecraftServerField == null)
minecraftServerField = FuzzyReflection.fromObject(server, true).getFieldByType(".*MinecraftServer");
try {
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
} catch (IllegalAccessException e1) {
logger.log(Level.WARNING, "Cannot extract minecraft server from Bukkit.");
return;
}
try {
if (serverConnectionMethod == null)
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
getMethodByParameters("getServerConnection", ".*ServerConnection", new String[] {});
// We're using Minecraft 1.3.1
injectServerConnection();
} catch (IllegalArgumentException e) {
// DEBUG
logger.log(Level.WARNING, "Reverting to old 1.2.5 server connection injection.", e);
// Minecraft 1.2.5 or lower
injectListenerThread();
} catch (Exception e) {
// Oh damn - inform the player
logger.log(Level.SEVERE, "Cannot inject into server connection. Bad things will happen.", e);
}
}
private void injectListenerThread() {
try {
if (listenerThreadField == null)
listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
getFieldByType(".*NetworkListenThread");
} catch (RuntimeException e) {
logger.log(Level.SEVERE, "Cannot find listener thread in MinecraftServer.", e);
return;
}
Object listenerThread = null;
// Attempt to get the thread
try {
listenerThread = listenerThreadField.get(minecraftServer);
} catch (Exception e) {
logger.log(Level.WARNING, "Unable to read the listener thread.", e);
return;
}
// Just inject every list field we can get
injectEveryListField(listenerThread, 1);
hasSuccess = true;
}
private void injectServerConnection() {
Object serverConnection = null;
// Careful - we might fail
try {
serverConnection = serverConnectionMethod.invoke(minecraftServer);
} catch (Exception ex) {
logger.log(Level.WARNING, "Unable to retrieve server connection", ex);
return;
}
if (listField == null)
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
getFieldByType("netServerHandlerList", List.class);
if (dedicatedThreadField == null) {
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(Thread.class);
// Verify the field count
if (matches.size() != 1)
logger.log(Level.WARNING, "Unexpected number of threads in " + serverConnection.getClass().getName());
else
dedicatedThreadField = matches.get(0);
}
// Next, try to get the dedicated thread
try {
if (dedicatedThreadField != null)
injectEveryListField(FieldUtils.readField(dedicatedThreadField, serverConnection, true), 1);
} catch (IllegalAccessException e) {
logger.log(Level.WARNING, "Unable to retrieve net handler thread.", e);
}
injectIntoList(serverConnection, listField);
hasSuccess = true;
}
/**
* Automatically inject into every List-compatible public or private field of the given object.
* @param container - container object with the fields to inject.
* @param minimum - the minimum number of fields we expect exists.
*/
private void injectEveryListField(Object container, int minimum) {
// Ok, great. Get every list field
List<Field> lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class);
for (Field list : lists) {
injectIntoList(container, list);
}
// Warn about unexpected errors
if (lists.size() < minimum) {
logger.log(Level.WARNING, "Unable to inject " + minimum + " lists in " + container.getClass().getName());
}
}
@SuppressWarnings("unchecked")
private void injectIntoList(Object instance, Field field) {
VolatileField listFieldRef = new VolatileField(field, instance, true);
List<Object> list = (List<Object>) listFieldRef.getValue();
// Careful not to inject twice
if (list instanceof ReplacedArrayList) {
replacedLists.add((ReplacedArrayList<Object>) list);
} else {
ReplacedArrayList<Object> injectedList = createReplacement(list);
replacedLists.add(injectedList);
listFieldRef.setValue(injectedList);
listFields.add(listFieldRef);
}
}
// Hack to avoid the "moved to quickly" error
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
return new ReplacedArrayList<Object>(list) {
/**
* Shut up Eclipse!
*/
private static final long serialVersionUID = 2070481080950500367L;
@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
ObjectCloner.copyTo(inserting, replacement, inserting.getClass());
}
}
@Override
protected void onInserting(Object inserting) {
// Ready for some login handler injection?
if (inserting instanceof NetLoginHandler) {
Object replaced = netLoginInjector.onNetLoginCreated(inserting);
// Only replace if it has changed
if (inserting != replaced)
addMapping(inserting, replaced, true);
}
}
@Override
protected void onRemoved(Object removing) {
// Clean up?
if (removing instanceof NetLoginHandler) {
netLoginInjector.cleanup(removing);
}
}
};
}
/**
* Replace the server handler instance kept by the "keep alive" object.
* @param oldHandler - old server handler.
* @param newHandler - new, proxied server handler.
*/
public void replaceServerHandler(Object oldHandler, Object newHandler) {
if (!hasAttempted) {
injectList();
}
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.addMapping(oldHandler, newHandler);
}
}
}
/**
* Revert to the old vanilla server handler, if it has been replaced.
* @param oldHandler - old vanilla server handler.
*/
public void revertServerHandler(Object oldHandler) {
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.removeMapping(oldHandler);
}
}
}
/**
* Undoes everything.
*/
public void cleanupAll() {
if (replacedLists.size() > 0) {
// Repair the underlying lists
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.revertAll();
}
for (VolatileField field : listFields) {
field.revertValue();
}
listFields.clear();
replacedLists.clear();
}
}
}
@@ -0,0 +1,76 @@
package com.comphenix.protocol.injector.player;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Represents a very quick integer set that uses a lookup table to store membership.
* <p>
* This class is intentionally missing a size method.
* @author Kristian
*/
class IntegerSet {
private final boolean[] array;
/**
* Initialize a lookup table with the given maximum number of elements.
* <p>
* This creates a set for elements in the range [0, count).
* <p>
* Formally, the current set must be a subset of [0, 1, 2, ..., count - 1].
* @param maximumCount - maximum element value and count.
*/
public IntegerSet(int maximumCount) {
this.array = new boolean[maximumCount];
}
/**
* Determine whether or not the given element exists in the set.
* @param value - the element to check. Must be in the range [0, count).
* @return TRUE if the given element exists, FALSE otherwise.
*/
public boolean contains(int element) {
return array[element];
}
/**
* Add the given element to the set, or do nothing if it already exists.
* @param element - element to add.
* @throws OutOfBoundsException If the given element is not in the range [0, count).
*/
public void add(int element) {
array[element] = true;
}
/**
* Remove the given element from the set, or do nothing if it's already removed.
* @param element - element to remove.
*/
public void remove(int element) {
// We don't actually care if the caller tries to remove an element outside the valid set
if (element >= 0 && element < array.length)
array[element] = false;
}
/**
* Remove every element from the set.
*/
public void clear() {
Arrays.fill(array, false);
}
/**
* Convert the current IntegerSet to an equivalent HashSet.
* @return The resulting HashSet.
*/
public Set<Integer> toSet() {
Set<Integer> elements = new HashSet<Integer>();
for (int i = 0; i < array.length; i++) {
if (array[i])
elements.add(i);
}
return elements;
}
}
@@ -0,0 +1,111 @@
package com.comphenix.protocol.injector.player;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer;
import com.google.common.collect.Maps;
/**
* Injects every NetLoginHandler created by the server.
*
* @author Kristian
*/
class NetLoginInjector {
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
// Handles every hook
private PlayerInjectionHandler injectionHandler;
private Server server;
// The current logger
private Logger logger;
private ReadWriteLock injectionLock = new ReentrantReadWriteLock();
// Used to create fake players
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
public NetLoginInjector(Logger logger, PlayerInjectionHandler injectionHandler, Server server) {
this.logger = logger;
this.injectionHandler = injectionHandler;
this.server = server;
}
/**
* Invoked when a NetLoginHandler has been created.
* @param inserting - the new NetLoginHandler.
* @return An injected NetLoginHandler, or the original object.
*/
public Object onNetLoginCreated(Object inserting) {
injectionLock.writeLock().lock();
try {
// Make sure we actually need to inject during this phase
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
return inserting;
Player fakePlayer = tempPlayerFactory.createTemporaryPlayer(server);
PlayerInjector injector = injectionHandler.injectPlayer(fakePlayer, inserting, GamePhase.LOGIN);
injector.updateOnLogin = true;
// Associate the injector too
InjectContainer container = (InjectContainer) fakePlayer;
container.setInjector(injector);
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
return inserting;
} catch (Throwable e) {
// Minecraft can't handle this, so we'll deal with it here
logger.log(Level.WARNING, "Unable to hook NetLoginHandler.", e);
return inserting;
} finally {
injectionLock.writeLock().unlock();
}
}
/**
* Retrieve the lock used for reading.
* @return Reading lock.
*/
public Lock getReadLock() {
return injectionLock.readLock();
}
/**
* Invoked when a NetLoginHandler should be reverted.
* @param inserting - the original NetLoginHandler.
* @return An injected NetLoginHandler, or the original object.
*/
public synchronized void cleanup(Object removing) {
PlayerInjector injected = injectedLogins.get(removing);
if (injected != null) {
injected.cleanupAll();
injectedLogins.remove(removing);
}
}
/**
* Remove all injected hooks.
*/
public void cleanupAll() {
for (PlayerInjector injector : injectedLogins.values()) {
injector.cleanupAll();
}
injectedLogins.clear();
}
}
@@ -1,179 +1,204 @@
package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.VolatileField;
import com.google.common.collect.Sets;
import net.minecraft.server.Packet;
/**
* Injection hook that overrides the packet queue lists in NetworkHandler.
*
* @author Kristian
*/
class NetworkFieldInjector extends PlayerInjector {
/**
* Marker interface that indicates a packet is fake and should not be processed.
* @author Kristian
*/
public interface FakePacket {
// Nothing
}
// Packets to ignore
private Set<Packet> ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap<Packet, Boolean>());
// Overridden fields
private List<VolatileField> overridenLists = new ArrayList<VolatileField>();
// Sync field
private static Field syncField;
private Object syncObject;
// Determine if we're listening
private Set<Integer> sendingFilters;
// Used to construct proxy objects
private ClassLoader classLoader;
public NetworkFieldInjector(ClassLoader classLoader, Logger logger, Player player,
ListenerInvoker manager, Set<Integer> sendingFilters) throws IllegalAccessException {
super(logger, player, manager);
this.classLoader = classLoader;
this.sendingFilters = sendingFilters;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public synchronized void initialize() throws IllegalAccessException {
super.initialize();
// Get the sync field as well
if (hasInitialized) {
if (syncField == null)
syncField = FuzzyReflection.fromObject(networkManager, true).getFieldByType("java\\.lang\\.Object");
syncObject = FieldUtils.readField(syncField, networkManager, true);
}
}
@Override
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
if (networkManager != null) {
try {
if (!filtered) {
ignoredPackets.add(packet);
}
// Note that invocation target exception is a wrapper for a checked exception
queueMethod.invoke(networkManager, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access queue method.", e);
}
} else {
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
}
}
@Override
public void checkListener(PacketListener listener) {
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
}
}
@Override
public void injectManager() {
if (networkManager != null) {
@SuppressWarnings("rawtypes")
StructureModifier<List> list = networkModifier.withType(List.class);
// Subclass both send queues
for (Field field : list.getFields()) {
VolatileField overwriter = new VolatileField(field, networkManager, true);
@SuppressWarnings("unchecked")
List<Packet> minecraftList = (List<Packet>) overwriter.getOldValue();
synchronized(syncObject) {
// The list we'll be inserting
List<Packet> hackedList = new InjectedArrayList(classLoader, this, ignoredPackets);
// Add every previously stored packet
for (Packet packet : minecraftList) {
hackedList.add(packet);
}
// Don' keep stale packets around
minecraftList.clear();
overwriter.setValue(Collections.synchronizedList(hackedList));
}
overridenLists.add(overwriter);
}
}
}
@SuppressWarnings("unchecked")
public void cleanupAll() {
// Clean up
for (VolatileField overriden : overridenLists) {
List<Packet> minecraftList = (List<Packet>) overriden.getOldValue();
List<Packet> hacketList = (List<Packet>) overriden.getValue();
if (minecraftList == hacketList) {
return;
}
// Get a lock before we modify the list
synchronized(syncObject) {
try {
// Copy over current packets
for (Packet packet : (List<Packet>) overriden.getValue()) {
minecraftList.add(packet);
}
} finally {
overriden.revertValue();
}
}
}
overridenLists.clear();
}
@Override
public boolean canInject() {
return true;
}
}
/*
* 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.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
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.StructureModifier;
import com.comphenix.protocol.reflect.VolatileField;
import com.google.common.collect.Sets;
import net.minecraft.server.Packet;
/**
* Injection hook that overrides the packet queue lists in NetworkHandler.
*
* @author Kristian
*/
class NetworkFieldInjector extends PlayerInjector {
/**
* Marker interface that indicates a packet is fake and should not be processed.
* @author Kristian
*/
public interface FakePacket {
// Nothing
}
// Packets to ignore
private Set<Packet> ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap<Packet, Boolean>());
// Overridden fields
private List<VolatileField> overridenLists = new ArrayList<VolatileField>();
// Sync field
private static Field syncField;
private Object syncObject;
// Determine if we're listening
private IntegerSet sendingFilters;
// Used to construct proxy objects
private ClassLoader classLoader;
public NetworkFieldInjector(ClassLoader classLoader, Logger logger, Player player,
ListenerInvoker manager, IntegerSet sendingFilters) throws IllegalAccessException {
super(logger, player, manager);
this.classLoader = classLoader;
this.sendingFilters = sendingFilters;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public synchronized void initialize(Object injectionSource) throws IllegalAccessException {
super.initialize(injectionSource);
// Get the sync field as well
if (hasInitialized) {
if (syncField == null)
syncField = FuzzyReflection.fromObject(networkManager, true).getFieldByType("java\\.lang\\.Object");
syncObject = FieldUtils.readField(syncField, networkManager, true);
}
}
@Override
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
if (networkManager != null) {
try {
if (!filtered) {
ignoredPackets.add(packet);
}
// Note that invocation target exception is a wrapper for a checked exception
queueMethod.invoke(networkManager, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access queue method.", e);
}
} else {
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
}
}
@Override
public void checkListener(PacketListener listener) {
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
}
}
@Override
public void injectManager() {
if (networkManager != null) {
@SuppressWarnings("rawtypes")
StructureModifier<List> list = networkModifier.withType(List.class);
// Subclass both send queues
for (Field field : list.getFields()) {
VolatileField overwriter = new VolatileField(field, networkManager, true);
@SuppressWarnings("unchecked")
List<Packet> minecraftList = (List<Packet>) overwriter.getOldValue();
synchronized(syncObject) {
// The list we'll be inserting
List<Packet> hackedList = new InjectedArrayList(classLoader, this, ignoredPackets);
// Add every previously stored packet
for (Packet packet : minecraftList) {
hackedList.add(packet);
}
// Don' keep stale packets around
minecraftList.clear();
overwriter.setValue(Collections.synchronizedList(hackedList));
}
overridenLists.add(overwriter);
}
}
}
@SuppressWarnings("unchecked")
public void cleanupAll() {
// Clean up
for (VolatileField overriden : overridenLists) {
List<Packet> minecraftList = (List<Packet>) overriden.getOldValue();
List<Packet> hacketList = (List<Packet>) overriden.getValue();
if (minecraftList == hacketList) {
return;
}
// Get a lock before we modify the list
synchronized(syncObject) {
try {
// Copy over current packets
for (Packet packet : (List<Packet>) overriden.getValue()) {
minecraftList.add(packet);
}
} finally {
overriden.revertValue();
}
}
}
overridenLists.clear();
}
@Override
public boolean canInject(GamePhase phase) {
// All phases should work
return true;
}
@Override
public PlayerInjectHooks getHookType() {
return PlayerInjectHooks.NETWORK_HANDLER_FIELDS;
}
}
@@ -0,0 +1,172 @@
/*
* 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;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.LazyLoader;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.logging.Logger;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
/**
* Injection method that overrides the NetworkHandler itself, and it's sendPacket-method.
*
* @author Kristian
*/
class NetworkObjectInjector extends PlayerInjector {
// Determine if we're listening
private IntegerSet sendingFilters;
// Used to construct proxy objects
private ClassLoader classLoader;
public NetworkObjectInjector(ClassLoader classLoader, Logger logger, Player player,
ListenerInvoker invoker, IntegerSet sendingFilters) throws IllegalAccessException {
super(logger, player, invoker);
this.sendingFilters = sendingFilters;
this.classLoader = classLoader;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue();
if (networkDelegate != null) {
try {
// Note that invocation target exception is a wrapper for a checked exception
queueMethod.invoke(networkDelegate, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access queue method.", e);
}
} else {
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
}
}
@Override
public void checkListener(PacketListener listener) {
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
}
}
@Override
public void injectManager() {
if (networkManager != null) {
final Class<?> networkInterface = networkManagerRef.getField().getType();
final Object networkDelegate = networkManagerRef.getOldValue();
if (!networkInterface.isInterface()) {
throw new UnsupportedOperationException(
"Must use CraftBukkit 1.3.0 or later to inject into into NetworkMananger.");
}
Callback queueFilter = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Packet packet = (Packet) args[0];
if (packet != null) {
packet = handlePacketSending(packet);
// A NULL packet indicate cancelling
if (packet != null)
args[0] = packet;
else
return null;
}
// Delegate to our underlying class
return proxy.invokeSuper(networkDelegate, args);
}
};
Callback dispatch = new LazyLoader() {
@Override
public Object loadObject() throws Exception {
return networkDelegate;
}
};
// Create our proxy object
Enhancer ex = new Enhancer();
ex.setClassLoader(classLoader);
ex.setSuperclass(networkInterface);
ex.setCallbacks(new Callback[] { queueFilter, dispatch });
ex.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.equals(queueMethod))
return 0;
else
return 1;
}
});
// Inject it, if we can.
networkManagerRef.setValue(ex.create());
}
}
@Override
public void cleanupAll() {
// Clean up
if (networkManagerRef != null && networkManagerRef.isCurrentSet()) {
networkManagerRef.revertValue();
}
}
@Override
public boolean canInject(GamePhase phase) {
// Works for all phases
return true;
}
@Override
public PlayerInjectHooks getHookType() {
return PlayerInjectHooks.NETWORK_MANAGER_OBJECT;
}
}
@@ -1,249 +1,273 @@
package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.logging.Logger;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectCloner;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
/**
* Represents a player hook into the NetServerHandler class.
*
* @author Kristian
*/
public class NetworkServerInjector extends PlayerInjector {
private static Method sendPacketMethod;
private InjectedServerConnection serverInjection;
// Determine if we're listening
private Set<Integer> sendingFilters;
// Used to create proxy objects
private ClassLoader classLoader;
public NetworkServerInjector(
ClassLoader classLoader, Logger logger, Player player,
ListenerInvoker invoker, Set<Integer> sendingFilters,
InjectedServerConnection serverInjection) throws IllegalAccessException {
super(logger, player, invoker);
this.classLoader = classLoader;
this.sendingFilters = sendingFilters;
this.serverInjection = serverInjection;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public void initialize() throws IllegalAccessException {
super.initialize();
// Get the send packet method!
if (hasInitialized) {
if (sendPacketMethod == null)
sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*");
}
}
@Override
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
Object serverDeleage = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
if (serverDeleage != null) {
try {
// Note that invocation target exception is a wrapper for a checked exception
sendPacketMethod.invoke(serverDeleage, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access send packet method.", e);
}
} else {
throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
}
}
@Override
public void injectManager() {
if (serverHandlerRef == null)
throw new IllegalStateException("Cannot find server handler.");
// Don't inject twice
if (serverHandlerRef.getValue() instanceof Factory)
return;
if (!tryInjectManager()) {
// Try to override the proxied object
if (proxyServerField != null) {
serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true);
serverHandler = serverHandlerRef.getValue();
if (serverHandler == null)
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
// Try again
if (tryInjectManager()) {
// It worked - probably
return;
}
}
throw new RuntimeException(
"Cannot hook player: Unable to find a valid constructor for the NetServerHandler object.");
}
}
private boolean tryInjectManager() {
Class<?> serverClass = serverHandler.getClass();
Enhancer ex = new Enhancer();
Callback sendPacketCallback = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Packet packet = (Packet) args[0];
if (packet != null) {
packet = handlePacketRecieved(packet);
// A NULL packet indicate cancelling
if (packet != null)
args[0] = packet;
else
return null;
}
// Call the method directly
return proxy.invokeSuper(obj, args);
};
};
Callback noOpCallback = NoOp.INSTANCE;
ex.setClassLoader(classLoader);
ex.setSuperclass(serverClass);
ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
ex.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.equals(sendPacketMethod))
return 0;
else
return 1;
}
});
// Find the Minecraft NetServerHandler superclass
Class<?> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass);
DefaultInstances serverInstances = null;
// Maybe the proxy instance can help?
Object proxyInstance = getProxyServerHandler();
// Use the existing server proxy when we create one
if (proxyInstance != null && proxyInstance != serverHandler) {
serverInstances = DefaultInstances.fromArray(generator,
ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
} else {
serverInstances = DefaultInstances.fromArray(generator);
}
serverInstances.setNonNull(true);
serverInstances.setMaximumRecursion(1);
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
// Inject it now
if (proxyObject != null) {
// This will be done by InjectedServerConnection instead
//copyTo(serverHandler, proxyObject);
serverInjection.replaceServerHandler(serverHandler, proxyObject);
serverHandlerRef.setValue(proxyObject);
return true;
} else {
return false;
}
}
private Object getProxyServerHandler() {
if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
try {
return FieldUtils.readField(proxyServerField, serverHandler, true);
} catch (Throwable e) {
// Oh well
}
}
return null;
}
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) {
if (clazz.getName().startsWith("net.minecraft.server."))
return clazz;
else if (clazz.equals(Object.class))
return clazz;
else
return getFirstMinecraftSuperClass(clazz.getSuperclass());
}
@Override
public void cleanupAll() {
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
ObjectCloner.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
serverHandlerRef.revertValue();
try {
if (getNetHandler() != null) {
// Restore packet listener
try {
FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
} catch (IllegalAccessException e) {
// Oh well
e.printStackTrace();
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
serverInjection.revertServerHandler(serverHandler);
}
@Override
public void checkListener(PacketListener listener) {
// We support everything
}
@Override
public boolean canInject() {
return true;
}
}
/*
* 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;
import java.lang.reflect.Method;
import java.util.logging.Logger;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
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.VolatileField;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
/**
* Represents a player hook into the NetServerHandler class.
*
* @author Kristian
*/
public class NetworkServerInjector extends PlayerInjector {
private static Method sendPacketMethod;
private InjectedServerConnection serverInjection;
// Determine if we're listening
private IntegerSet sendingFilters;
// Used to create proxy objects
private ClassLoader classLoader;
public NetworkServerInjector(
ClassLoader classLoader, Logger logger, Player player,
ListenerInvoker invoker, IntegerSet sendingFilters,
InjectedServerConnection serverInjection) throws IllegalAccessException {
super(logger, player, invoker);
this.classLoader = classLoader;
this.sendingFilters = sendingFilters;
this.serverInjection = serverInjection;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public void initialize(Object injectionSource) throws IllegalAccessException {
super.initialize(injectionSource);
// Get the send packet method!
if (hasInitialized) {
if (sendPacketMethod == null)
sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*");
}
}
@Override
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
Object serverDeleage = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
if (serverDeleage != null) {
try {
// Note that invocation target exception is a wrapper for a checked exception
sendPacketMethod.invoke(serverDeleage, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access send packet method.", e);
}
} else {
throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
}
}
@Override
public void injectManager() {
if (serverHandlerRef == null)
throw new IllegalStateException("Cannot find server handler.");
// Don't inject twice
if (serverHandlerRef.getValue() instanceof Factory)
return;
if (!tryInjectManager()) {
// Try to override the proxied object
if (proxyServerField != null) {
serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true);
serverHandler = serverHandlerRef.getValue();
if (serverHandler == null)
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
// Try again
if (tryInjectManager()) {
// It worked - probably
return;
}
}
throw new RuntimeException(
"Cannot hook player: Unable to find a valid constructor for the NetServerHandler object.");
}
}
private boolean tryInjectManager() {
Class<?> serverClass = serverHandler.getClass();
Enhancer ex = new Enhancer();
Callback sendPacketCallback = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Packet packet = (Packet) args[0];
if (packet != null) {
packet = handlePacketSending(packet);
// A NULL packet indicate cancelling
if (packet != null)
args[0] = packet;
else
return null;
}
// Call the method directly
return proxy.invokeSuper(obj, args);
};
};
Callback noOpCallback = NoOp.INSTANCE;
ex.setClassLoader(classLoader);
ex.setSuperclass(serverClass);
ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
ex.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.equals(sendPacketMethod))
return 0;
else
return 1;
}
});
// Find the Minecraft NetServerHandler superclass
Class<?> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass);
DefaultInstances serverInstances = null;
// Maybe the proxy instance can help?
Object proxyInstance = getProxyServerHandler();
// Use the existing server proxy when we create one
if (proxyInstance != null && proxyInstance != serverHandler) {
serverInstances = DefaultInstances.fromArray(generator,
ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
} else {
serverInstances = DefaultInstances.fromArray(generator);
}
serverInstances.setNonNull(true);
serverInstances.setMaximumRecursion(1);
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
// Inject it now
if (proxyObject != null) {
// This will be done by InjectedServerConnection instead
//copyTo(serverHandler, proxyObject);
serverInjection.replaceServerHandler(serverHandler, proxyObject);
serverHandlerRef.setValue(proxyObject);
return true;
} else {
return false;
}
}
private Object getProxyServerHandler() {
if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
try {
return FieldUtils.readField(proxyServerField, serverHandler, true);
} catch (Throwable e) {
// Oh well
}
}
return null;
}
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) {
if (clazz.getName().startsWith("net.minecraft.server."))
return clazz;
else if (clazz.equals(Object.class))
return clazz;
else
return getFirstMinecraftSuperClass(clazz.getSuperclass());
}
@Override
public void cleanupAll() {
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
ObjectCloner.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
serverHandlerRef.revertValue();
try {
if (getNetHandler() != null) {
// Restore packet listener
try {
FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
} catch (IllegalAccessException e) {
// Oh well
e.printStackTrace();
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
serverInjection.revertServerHandler(serverHandler);
}
@Override
public void checkListener(PacketListener listener) {
// We support everything
}
@Override
public boolean canInject(GamePhase phase) {
// Doesn't work when logging in
return phase == GamePhase.PLAYING;
}
@Override
public PlayerInjectHooks getHookType() {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
}
@@ -0,0 +1,540 @@
/*
* 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.io.DataInputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.server.Packet;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
/**
* Responsible for injecting into a player's sendPacket method.
*
* @author Kristian
*/
public class PlayerInjectionHandler {
/**
* The highest possible packet ID. It's unlikely that this value will ever change.
*/
private static final int MAXIMUM_PACKET_ID = 255;
// Server connection injection
private InjectedServerConnection serverInjection;
// NetLogin injector
private NetLoginInjector netLoginInjector;
// The last successful player hook
private PlayerInjector lastSuccessfulHook;
// Player injection
private Map<SocketAddress, PlayerInjector> addressLookup = Maps.newConcurrentMap();
private Map<DataInputStream, PlayerInjector> dataInputLookup = Maps.newConcurrentMap();
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
// Player injection types
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// Error logger
private Logger logger;
// Whether or not we're closing
private boolean hasClosed;
// Used to invoke events
private ListenerInvoker invoker;
// Enabled packet filters
private IntegerSet sendingFilters = new IntegerSet(MAXIMUM_PACKET_ID + 1);
// The class loader we're using
private ClassLoader classLoader;
// Used to filter injection attempts
private Predicate<GamePhase> injectionFilter;
public PlayerInjectionHandler(ClassLoader classLoader, Logger logger, Predicate<GamePhase> injectionFilter,
ListenerInvoker invoker, Server server) {
this.classLoader = classLoader;
this.logger = logger;
this.invoker = invoker;
this.injectionFilter = injectionFilter;
this.netLoginInjector = new NetLoginInjector(logger, this, server);
this.serverInjection = new InjectedServerConnection(logger, server, netLoginInjector);
serverInjection.injectList();
}
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
public PlayerInjectHooks getPlayerHook() {
return getPlayerHook(GamePhase.PLAYING);
}
/**
* Retrieves how the server packets are read.
* @param phase - the current game phase.
* @return Injection method for reading server packets.
*/
public PlayerInjectHooks getPlayerHook(GamePhase phase) {
switch (phase) {
case LOGIN:
return loginPlayerHook;
case PLAYING:
return playingPlayerHook;
default:
throw new IllegalArgumentException("Cannot retrieve injection hook for both phases at the same time.");
}
}
/**
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
public void setPlayerHook(PlayerInjectHooks playerHook) {
setPlayerHook(GamePhase.PLAYING, playerHook);
}
/**
* Sets how the server packets are read.
* @param phase - the current game phase.
* @param playerHook - the new injection method for reading server packets.
*/
public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) {
if (phase.hasLogin())
loginPlayerHook = playerHook;
if (phase.hasPlaying())
playingPlayerHook = playerHook;
}
/**
* Add an underlying packet handler of the given ID.
* @param packetID - packet ID to register.
*/
public void addPacketHandler(int packetID) {
sendingFilters.add(packetID);
}
/**
* Remove an underlying packet handler of ths ID.
* @param packetID - packet ID to unregister.
*/
public void removePacketHandler(int packetID) {
sendingFilters.remove(packetID);
}
/**
* Used to construct a player hook.
* @param player - the player to hook.
* @param hook - the hook type.
* @return A new player hoook
* @throws IllegalAccessException Unable to do our reflection magic.
*/
private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
// Construct the correct player hook
switch (hook) {
case NETWORK_HANDLER_FIELDS:
return new NetworkFieldInjector(classLoader, logger, player, invoker, sendingFilters);
case NETWORK_MANAGER_OBJECT:
return new NetworkObjectInjector(classLoader, logger, player, invoker, sendingFilters);
case NETWORK_SERVER_OBJECT:
return new NetworkServerInjector(classLoader, logger, player, invoker, sendingFilters, serverInjection);
default:
throw new IllegalArgumentException("Cannot construct a player injector.");
}
}
/**
* Retrieve a player by its DataInput connection.
* @param inputStream - the associated DataInput connection.
* @return The player.
*/
public Player getPlayerByConnection(DataInputStream inputStream) {
try {
// Concurrency issue!
netLoginInjector.getReadLock().lock();
PlayerInjector injector = dataInputLookup.get(inputStream);
if (injector != null) {
return injector.getPlayer();
} else {
System.out.println("Unable to find stream: " + inputStream);
return null;
}
} finally {
netLoginInjector.getReadLock().unlock();
}
}
private PlayerInjectHooks getInjectorType(PlayerInjector injector) {
return injector != null ? injector.getHookType() : PlayerInjectHooks.NONE;
}
/**
* Initialize a player hook, allowing us to read server packets.
* <p>
* This call will be ignored if there's no listener that can receive the given events.
* @param player - player to hook.
*/
public void injectPlayer(Player player) {
// Inject using the player instance itself
if (isInjectionNecessary(GamePhase.PLAYING)) {
injectPlayer(player, player, GamePhase.PLAYING);
}
}
/**
* Determine if it's truly necessary to perform the given player injection.
* @param phase - current game phase.
* @return TRUE if we should perform the injection, FALSE otherwise.
*/
public boolean isInjectionNecessary(GamePhase phase) {
return injectionFilter.apply(phase);
}
/**
* Initialize a player hook, allowing us to read server packets.
* <p>
* This method will always perform the instructed injection.
*
* @param player - player to hook.
* @param injectionPoint - the object to use during the injection process.
* @param phase - the current game phase.
* @return The resulting player injector, or NULL if the injection failed.
*/
PlayerInjector injectPlayer(Player player, Object injectionPoint, GamePhase phase) {
// Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method.
synchronized (player) {
return injectPlayerInternal(player, injectionPoint, phase);
}
}
// Unsafe variant of the above
private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, GamePhase phase) {
PlayerInjector injector = playerInjection.get(player);
PlayerInjectHooks tempHook = getPlayerHook(phase);
PlayerInjectHooks permanentHook = tempHook;
// The given player object may be fake, so be careful!
// See if we need to inject something else
boolean invalidInjector = injector != null ? !injector.canInject(phase) : true;
// Don't inject if the class has closed
if (!hasClosed && player != null && (tempHook != getInjectorType(injector) || invalidInjector)) {
while (tempHook != PlayerInjectHooks.NONE) {
// Whether or not the current hook method failed completely
boolean hookFailed = false;
// Remove the previous hook, if any
cleanupHook(injector);
try {
injector = getHookInstance(player, tempHook);
// Make sure this injection method supports the current game phase
if (injector.canInject(phase)) {
injector.initialize(injectionPoint);
DataInputStream inputStream = injector.getInputStream(false);
Socket socket = injector.getSocket();
SocketAddress address = socket.getRemoteSocketAddress();
// Make sure the current player is not logged out
if (socket.isClosed()) {
throw new PlayerLoggedOutException();
}
PlayerInjector previous = addressLookup.get(address);
// Close any previously associated hooks before we proceed
if (previous != null) {
uninjectPlayer(previous.getPlayer());
// Remove the "hooked" network manager in our instance as well
if (previous instanceof NetworkObjectInjector) {
injector.setNetworkManager(previous.getNetworkManager(), true);
}
}
injector.injectManager();
dataInputLookup.put(inputStream, injector);
addressLookup.put(address, injector);
break;
}
} catch (PlayerLoggedOutException e) {
throw e;
} catch (Exception e) {
// Mark this injection attempt as a failure
logger.log(Level.SEVERE, "Player hook " + tempHook.toString() + " failed.", e);
hookFailed = true;
}
// Choose the previous player hook type
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
if (hookFailed)
logger.log(Level.INFO, "Switching to " + tempHook.toString() + " instead.");
// Check for UTTER FAILURE
if (tempHook == PlayerInjectHooks.NONE) {
cleanupHook(injector);
injector = null;
hookFailed = true;
}
// Should we set the default hook method too?
if (hookFailed) {
permanentHook = tempHook;
}
}
// Update values
if (injector != null)
lastSuccessfulHook = injector;
if (permanentHook != getPlayerHook(phase))
setPlayerHook(phase, tempHook);
// Save last injector
playerInjection.put(player, injector);
}
return injector;
}
private void cleanupHook(PlayerInjector injector) {
// Clean up as much as possible
try {
if (injector != null)
injector.cleanupAll();
} catch (Exception e2) {
logger.log(Level.WARNING, "Cleaing up after player hook failed.", e2);
}
}
/**
* Unregisters the given player.
* @param player - player to unregister.
*/
public void uninjectPlayer(Player player) {
if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.remove(player);
if (injector != null) {
DataInputStream input = injector.getInputStream(true);
InetSocketAddress address = player.getAddress();
injector.cleanupAll();
dataInputLookup.remove(input);
if (address != null)
addressLookup.remove(address);
}
}
}
/**
* Unregisters a player by the given address.
* @param address - address of the player to unregister.
*/
public void uninjectPlayer(InetSocketAddress address) {
if (!hasClosed && address != null) {
PlayerInjector injector = addressLookup.get(address);
// Clean up
if (injector != null)
uninjectPlayer(injector.getPlayer());
}
}
/**
* Send the given packet to the given reciever.
* @param reciever - the player receiver.
* @param packet - the packet to send.
* @param filters - whether or not to invoke the packet filters.
* @throws InvocationTargetException If an error occured during sending.
*/
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
PlayerInjector injector = getInjector(reciever);
// Send the packet, or drop it completely
if (injector != null)
injector.sendServerPacket(packet.getHandle(), filters);
else
logger.log(Level.WARNING, String.format(
"Unable to send packet %s (%s): Player %s has logged out.",
packet.getID(), packet, reciever.getName()
));
}
/**
* Process a packet as if it were sent by the given player.
* @param player - the sender.
* @param mcPacket - the packet to process.
* @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error.
*/
public void processPacket(Player player, Packet mcPacket) throws IllegalAccessException, InvocationTargetException {
PlayerInjector injector = getInjector(player);
// Process the given packet, or simply give up
if (injector != null)
injector.processPacket(mcPacket);
else
logger.log(Level.WARNING, String.format(
"Unable to receieve packet %s. Player %s has logged out.",
mcPacket, player.getName()
));
}
/**
* Retrieve the injector associated with this player.
* @param player - the player to find.
* @return The injector, or NULL if not found.
*/
private PlayerInjector getInjector(Player player) {
return playerInjection.get(player);
}
/**
* Determine if the given listeners are valid.
* @param listeners - listeners to check.
*/
public void checkListener(Set<PacketListener> listeners) {
// Make sure the current listeners are compatible
if (lastSuccessfulHook != null) {
for (PacketListener listener : listeners) {
try {
checkListener(listener);
} catch (IllegalStateException e) {
logger.log(Level.WARNING, "Unsupported listener.", e);
}
}
}
}
/**
* Determine if a listener is valid or not.
* @param listener - listener to check.
* @throws IllegalStateException If the given listener's whitelist cannot be fulfilled.
*/
public void checkListener(PacketListener listener) {
try {
if (lastSuccessfulHook != null)
lastSuccessfulHook.checkListener(listener);
} catch (Exception e) {
throw new IllegalStateException("Registering listener " + PacketAdapter.getPluginName(listener) + " failed", e);
}
}
/**
* Retrieve the current list of registered sending listeners.
* @return List of the sending listeners's packet IDs.
*/
public Set<Integer> getSendingFilters() {
return sendingFilters.toSet();
}
/**
* Retrieve the current logger.
* @return Error logger.
*/
public Logger getLogger() {
return logger;
}
public void close() {
// Guard
if (hasClosed || playerInjection == null)
return;
// Remove everything
for (PlayerInjector injection : playerInjection.values()) {
if (injection != null) {
injection.cleanupAll();
}
}
// Remove server handler
if (serverInjection != null)
serverInjection.cleanupAll();
if (netLoginInjector != null)
netLoginInjector.cleanupAll();
serverInjection = null;
netLoginInjector = null;
hasClosed = true;
playerInjection.clear();
dataInputLookup.clear();
addressLookup.clear();
invoker = null;
}
/**
* Inform the current PlayerInjector that it should update the DataInputStream next.
* @param player - the player to update.
*/
public void scheduleDataInputRefresh(Player player) {
final PlayerInjector injector = getInjector(player);
final DataInputStream old = injector.getInputStream(true);
// Update the DataInputStream
if (injector != null) {
injector.scheduleAction(new Runnable() {
@Override
public void run() {
dataInputLookup.remove(old);
dataInputLookup.put(injector.getInputStream(false), injector);
}
});
}
}
}
@@ -1,340 +1,557 @@
/*
* 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.io.DataInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.server.EntityPlayer;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Factory;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.VolatileField;
abstract class PlayerInjector {
// Cache previously retrieved fields
protected static Field serverHandlerField;
protected static Field proxyServerField;
protected static Field networkManagerField;
protected static Field inputField;
protected static Field netHandlerField;
// Whether or not we're using a proxy type
private static boolean hasProxyType;
// To add our injected array lists
protected static StructureModifier<Object> networkModifier;
// And methods
protected static Method queueMethod;
protected static Method processMethod;
protected Player player;
protected boolean hasInitialized;
// Reference to the player's network manager
protected VolatileField networkManagerRef;
protected VolatileField serverHandlerRef;
protected Object networkManager;
// Current net handler
protected Object serverHandler;
protected Object netHandler;
// The packet manager and filters
protected ListenerInvoker invoker;
// Previous data input
protected DataInputStream cachedInput;
// Handle errors
protected Logger logger;
public PlayerInjector(Logger logger, Player player, ListenerInvoker invoker) throws IllegalAccessException {
this.logger = logger;
this.player = player;
this.invoker = invoker;
}
/**
* Retrieve the notch (NMS) entity player object.
* @return Notch player object.
*/
protected EntityPlayer getEntityPlayer() {
CraftPlayer craft = (CraftPlayer) player;
return craft.getHandle();
}
/**
* Initialize all fields for this player injector, if it hasn't already.
* @throws IllegalAccessException An error has occured.
*/
public void initialize() throws IllegalAccessException {
EntityPlayer notchEntity = getEntityPlayer();
if (!hasInitialized) {
// Do this first, in case we encounter an exception
hasInitialized = true;
// Retrieve the server handler
if (serverHandlerField == null) {
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler");
proxyServerField = getProxyField(notchEntity, serverHandlerField);
}
// Yo dawg
serverHandlerRef = new VolatileField(serverHandlerField, notchEntity);
serverHandler = serverHandlerRef.getValue();
// Next, get the network manager
if (networkManagerField == null)
networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(".*NetworkManager");
networkManagerRef = new VolatileField(networkManagerField, serverHandler);
networkManager = networkManagerRef.getValue();
// Create the network manager modifier from the actual object type
if (networkManager != null && networkModifier == null)
networkModifier = new StructureModifier<Object>(networkManager.getClass(), null, false);
// And the queue method
if (queueMethod == null)
queueMethod = FuzzyReflection.fromClass(networkManagerField.getType()).
getMethodByParameters("queue", Packet.class );
// And the data input stream that we'll use to identify a player
if (inputField == null)
inputField = FuzzyReflection.fromObject(networkManager, true).
getFieldByType("java\\.io\\.DataInputStream");
}
}
/**
* Retrieve whether or not the server handler is a proxy object.
* @return TRUE if it is, FALSE otherwise.
*/
protected boolean hasProxyServerHandler() {
return hasProxyType;
}
private Field getProxyField(EntityPlayer notchEntity, Field serverField) {
try {
Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true);
// Is this a Minecraft hook?
if (handler != null && !handler.getClass().getName().startsWith("net.minecraft.server")) {
// This is our proxy object
if (handler instanceof Factory)
return null;
hasProxyType = true;
logger.log(Level.WARNING, "Detected server handler proxy type by another plugin. Conflict may occur!");
// No? Is it a Proxy type?
try {
FuzzyReflection reflection = FuzzyReflection.fromObject(handler, true);
// It might be
return reflection.getFieldByType(".*NetServerHandler");
} catch (RuntimeException e) {
// Damn
}
}
} catch (IllegalAccessException e) {
logger.warning("Unable to load server handler from proxy type.");
}
// Nope, just go with it
return null;
}
/**
* Retrieves the current net handler for this player.
* @return Current net handler.
* @throws IllegalAccessException Unable to find or retrieve net handler.
*/
protected Object getNetHandler() throws IllegalAccessException {
// What a mess
try {
if (netHandlerField == null)
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
getFieldByType("net\\.minecraft\\.NetHandler");
} catch (RuntimeException e1) {
// Swallow it
}
// Second attempt
if (netHandlerField == null) {
try {
// Well, that sucks. Try just Minecraft objects then.
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
} catch (RuntimeException e2) {
throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
}
}
// Get the handler
if (netHandler == null)
netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
return netHandler;
}
/**
* Processes the given packet as if it was transmitted by the current player.
* @param packet - packet to process.
* @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error.
*/
public void processPacket(Packet packet) throws IllegalAccessException, InvocationTargetException {
Object netHandler = getNetHandler();
// Get the process method
if (processMethod == null) {
try {
processMethod = FuzzyReflection.fromClass(Packet.class).
getMethodByParameters("processPacket", netHandlerField.getType());
} catch (RuntimeException e) {
throw new IllegalArgumentException("Cannot locate process packet method: " + e.getMessage());
}
}
// We're ready
try {
processMethod.invoke(packet, netHandler);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Method " + processMethod.getName() + " is not compatible.");
} catch (InvocationTargetException e) {
throw e;
}
}
/**
* Send a packet to the client.
* @param packet - server packet to send.
* @param filtered - whether or not the packet will be filtered by our listeners.
* @param InvocationTargetException If an error occured when sending the packet.
*/
public abstract void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException;
/**
* Inject a hook to catch packets sent to the current player.
*/
public abstract void injectManager();
/**
* Remove all hooks and modifications.
*/
public abstract void cleanupAll();
/**
* Determine if this inject method can even be attempted.
* @return TRUE if can be attempted, though possibly with failure, FALSE otherwise.
*/
public abstract boolean canInject();
/**
* Invoked before a new listener is registered.
* <p>
* The player injector should throw an exception if this listener cannot be properly supplied with packet events.
* @param listener - the listener that is about to be registered.
*/
public abstract void checkListener(PacketListener listener);
/**
* Allows a packet to be recieved by the listeners.
* @param packet - packet to recieve.
* @return The given packet, or the packet replaced by the listeners.
*/
public Packet handlePacketRecieved(Packet packet) {
// Get the packet ID too
Integer id = invoker.getPacketID(packet);
// Make sure we're listening
if (id != null && hasListener(id)) {
// A packet has been sent guys!
PacketContainer container = new PacketContainer(id, packet);
PacketEvent event = PacketEvent.fromServer(invoker, container, player);
invoker.invokePacketSending(event);
// Cancelling is pretty simple. Just ignore the packet.
if (event.isCancelled())
return null;
// Right, remember to replace the packet again
return event.getPacket().getHandle();
}
return packet;
}
/**
* Determine if the given injector is listening for this packet ID.
* @param packetID - packet ID to check.
* @return TRUE if it is, FALSE oterhwise.
*/
protected abstract boolean hasListener(int packetID);
/**
* Retrieve the current player's input stream.
* @param cache - whether or not to cache the result of this method.
* @return The player's input stream.
*/
public DataInputStream getInputStream(boolean cache) {
if (inputField == null)
throw new IllegalStateException("Input field is NULL.");
if (networkManager == null)
throw new IllegalStateException("Network manager is NULL.");
// Get the associated input stream
try {
if (cache && cachedInput != null)
return cachedInput;
// Save to cache
cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager, true);
return cachedInput;
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read input stream.", e);
}
}
}
/*
* 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.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.server.EntityPlayer;
import net.minecraft.server.NetLoginHandler;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Factory;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
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.StructureModifier;
import com.comphenix.protocol.reflect.VolatileField;
abstract class PlayerInjector {
// Net login handler stuff
private static Field netLoginNetworkField;
// Different disconnect methods
private static Method loginDisconnect;
private static Method serverDisconnect;
// Cache previously retrieved fields
protected static Field serverHandlerField;
protected static Field proxyServerField;
protected static Field networkManagerField;
protected static Field inputField;
protected static Field netHandlerField;
protected static Field socketField;
private static Field entityPlayerField;
// Whether or not we're using a proxy type
private static boolean hasProxyType;
// To add our injected array lists
protected static StructureModifier<Object> networkModifier;
// And methods
protected static Method queueMethod;
protected static Method processMethod;
protected Player player;
protected boolean hasInitialized;
// Reference to the player's network manager
protected VolatileField networkManagerRef;
protected VolatileField serverHandlerRef;
protected Object networkManager;
// Current net handler
protected Object loginHandler;
protected Object serverHandler;
protected Object netHandler;
// Current socket
protected Socket socket;
// The packet manager and filters
protected ListenerInvoker invoker;
// Previous data input
protected DataInputStream cachedInput;
// Handle errors
protected Logger logger;
// Scheduled action on the next packet event
protected Runnable scheduledAction;
// Whether or not to update the current player on the first Packet1Login
boolean updateOnLogin;
Player updatedPlayer;
public PlayerInjector(Logger logger, Player player, ListenerInvoker invoker) throws IllegalAccessException {
this.logger = logger;
this.player = player;
this.invoker = invoker;
}
/**
* Retrieve the notch (NMS) entity player object.
* @param player - the player to retrieve.
* @return Notch player object.
*/
protected EntityPlayer getEntityPlayer(Player player) {
CraftPlayer craft = (CraftPlayer) player;
return craft.getHandle();
}
/**
* Initialize all fields for this player injector, if it hasn't already.
* @throws IllegalAccessException An error has occured.
*/
public void initialize(Object injectionSource) throws IllegalAccessException {
if (injectionSource == null)
throw new IllegalArgumentException("injectionSource cannot be NULL");
//Dispatch to the correct injection method
if (injectionSource instanceof Player)
initializePlayer(injectionSource);
else if (injectionSource instanceof NetLoginHandler)
initializeLogin(injectionSource);
else
throw new IllegalArgumentException("Cannot initialize a player hook using a " + injectionSource.getClass().getName());
}
/**
* Initialize the player injector using an actual player instance.
* @param player - the player to hook.
*/
public void initializePlayer(Object player) {
EntityPlayer notchEntity = getEntityPlayer((Player) player);
if (!hasInitialized) {
// Do this first, in case we encounter an exception
hasInitialized = true;
// Retrieve the server handler
if (serverHandlerField == null) {
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler");
proxyServerField = getProxyField(notchEntity, serverHandlerField);
}
// Yo dawg
serverHandlerRef = new VolatileField(serverHandlerField, notchEntity);
serverHandler = serverHandlerRef.getValue();
// Next, get the network manager
if (networkManagerField == null)
networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(".*NetworkManager");
initializeNetworkManager(networkManagerField, serverHandler);
}
}
/**
* Initialize the player injector for a NetLoginHandler instead.
* @param netLoginHandler - the net login handler to inject.
*/
public void initializeLogin(Object netLoginHandler) {
if (!hasInitialized) {
hasInitialized = true;
loginHandler = netLoginHandler;
if (netLoginNetworkField == null)
netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler).getFieldByType(".*NetworkManager");
initializeNetworkManager(netLoginNetworkField, netLoginHandler);
}
}
private void initializeNetworkManager(Field reference, Object container) {
networkManagerRef = new VolatileField(reference, container);
networkManager = networkManagerRef.getValue();
// No, don't do it
if (networkManager instanceof Factory) {
return;
}
// Create the network manager modifier from the actual object type
if (networkManager != null && networkModifier == null)
networkModifier = new StructureModifier<Object>(networkManager.getClass(), null, false);
// And the queue method
if (queueMethod == null)
queueMethod = FuzzyReflection.fromClass(reference.getType()).
getMethodByParameters("queue", Packet.class );
// And the data input stream that we'll use to identify a player
if (inputField == null)
inputField = FuzzyReflection.fromObject(networkManager, true).
getFieldByType("java\\.io\\.DataInputStream");
}
/**
* Retrieve whether or not the server handler is a proxy object.
* @return TRUE if it is, FALSE otherwise.
*/
protected boolean hasProxyServerHandler() {
return hasProxyType;
}
/**
* Retrieve the current network manager.
* @return Current network manager.
*/
public Object getNetworkManager() {
return networkManagerRef.getValue();
}
/**
* Set the current network manager.
* @param value - new network manager.
* @param force - whether or not to save this value.
*/
public void setNetworkManager(Object value, boolean force) {
networkManagerRef.setValue(value);
if (force)
networkManagerRef.saveValue();
initializeNetworkManager(networkManagerField, serverHandler);
}
/**
* Retrieve the associated socket of this player.
* @return The associated socket.
* @throws IllegalAccessException If we're unable to read the socket field.
*/
public Socket getSocket() throws IllegalAccessException {
try {
if (socketField == null)
socketField = FuzzyReflection.fromObject(networkManager).getFieldListByType(Socket.class).get(0);
if (socket == null)
socket = (Socket) FieldUtils.readField(socketField, networkManager);
return socket;
} catch (IndexOutOfBoundsException e) {
throw new IllegalAccessException("Unable to read the socket field.");
}
}
/**
* Attempt to disconnect the current client.
* @param message - the message to display.
* @throws InvocationTargetException If disconnection failed.
*/
public void disconnect(String message) throws InvocationTargetException {
// Get a non-null handler
boolean usingNetServer = serverHandler != null;
Object handler = usingNetServer ? serverHandler : loginHandler;
Method disconnect = usingNetServer ? serverDisconnect : loginDisconnect;
// Execute disconnect on it
if (handler != null) {
if (disconnect == null) {
disconnect = FuzzyReflection.fromObject(handler).getMethodByName("disconnect.*");
// Save the method for later
if (usingNetServer)
serverDisconnect = disconnect;
else
loginDisconnect = disconnect;
}
try {
disconnect.invoke(handler, message);
return;
} catch (IllegalArgumentException e) {
logger.log(Level.WARNING, "Invalid argument passed to disconnect method: " + message, e);
} catch (IllegalAccessException e) {
logger.log(Level.SEVERE, "Unable to access disconnect method.", e);
}
}
// Fuck it
try {
getSocket().close();
} catch (IOException e) {
logger.log(Level.SEVERE, "Unable to close socket.", e);
} catch (IllegalAccessException e) {
logger.log(Level.SEVERE, "Insufficient permissions. Cannot close socket.", e);
}
}
private Field getProxyField(EntityPlayer notchEntity, Field serverField) {
try {
Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true);
// Is this a Minecraft hook?
if (handler != null && !handler.getClass().getName().startsWith("net.minecraft.server")) {
// This is our proxy object
if (handler instanceof Factory)
return null;
hasProxyType = true;
logger.log(Level.WARNING, "Detected server handler proxy type by another plugin. Conflict may occur!");
// No? Is it a Proxy type?
try {
FuzzyReflection reflection = FuzzyReflection.fromObject(handler, true);
// It might be
return reflection.getFieldByType(".*NetServerHandler");
} catch (RuntimeException e) {
// Damn
}
}
} catch (IllegalAccessException e) {
logger.warning("Unable to load server handler from proxy type.");
}
// Nope, just go with it
return null;
}
/**
* Retrieves the current net handler for this player.
* @return Current net handler.
* @throws IllegalAccessException Unable to find or retrieve net handler.
*/
protected Object getNetHandler() throws IllegalAccessException {
// What a mess
try {
if (netHandlerField == null)
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
getFieldByType("net\\.minecraft\\.NetHandler");
} catch (RuntimeException e1) {
// Swallow it
}
// Second attempt
if (netHandlerField == null) {
try {
// Well, that sucks. Try just Minecraft objects then.
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
} catch (RuntimeException e2) {
throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
}
}
// Get the handler
if (netHandler == null)
netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
return netHandler;
}
/**
* Retrieve the stored entity player from a given NetHandler.
* @param netHandler - the nethandler to retrieve it from.
* @return The stored entity player.
* @throws IllegalAccessException If the reflection failed.
*/
private EntityPlayer getEntityPlayer(Object netHandler) throws IllegalAccessException {
if (entityPlayerField == null)
entityPlayerField = FuzzyReflection.fromObject(netHandler).getFieldByType(".*EntityPlayer");
return (EntityPlayer) FieldUtils.readField(entityPlayerField, netHandler);
}
/**
* Processes the given packet as if it was transmitted by the current player.
* @param packet - packet to process.
* @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error.
*/
public void processPacket(Packet packet) throws IllegalAccessException, InvocationTargetException {
Object netHandler = getNetHandler();
// Get the process method
if (processMethod == null) {
try {
processMethod = FuzzyReflection.fromClass(Packet.class).
getMethodByParameters("processPacket", netHandlerField.getType());
} catch (RuntimeException e) {
throw new IllegalArgumentException("Cannot locate process packet method: " + e.getMessage());
}
}
// We're ready
try {
processMethod.invoke(packet, netHandler);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Method " + processMethod.getName() + " is not compatible.");
} catch (InvocationTargetException e) {
throw e;
}
}
/**
* Send a packet to the client.
* @param packet - server packet to send.
* @param filtered - whether or not the packet will be filtered by our listeners.
* @param InvocationTargetException If an error occured when sending the packet.
*/
public abstract void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException;
/**
* Inject a hook to catch packets sent to the current player.
*/
public abstract void injectManager();
/**
* Remove all hooks and modifications.
*/
public abstract void cleanupAll();
/**
* Determine if this inject method can even be attempted.
* @return TRUE if can be attempted, though possibly with failure, FALSE otherwise.
*/
public abstract boolean canInject(GamePhase state);
/**
* Retrieve the hook type this class represents.
* @return Hook type this class represents.
*/
public abstract PlayerInjectHooks getHookType();
/**
* Invoked before a new listener is registered.
* <p>
* The player injector should throw an exception if this listener cannot be properly supplied with packet events.
* @param listener - the listener that is about to be registered.
*/
public abstract void checkListener(PacketListener listener);
/**
* Allows a packet to be sent by the listeners.
* @param packet - packet to sent.
* @return The given packet, or the packet replaced by the listeners.
*/
public Packet handlePacketSending(Packet packet) {
// Get the packet ID too
Integer id = invoker.getPacketID(packet);
Player currentPlayer = player;
// Hack #1: Handle a single scheduled action
if (scheduledAction != null) {
scheduledAction.run();
scheduledAction = null;
}
// Hack #2
if (updateOnLogin) {
if (id == Packets.Server.LOGIN) {
try {
updatedPlayer = getEntityPlayer(getNetHandler()).getBukkitEntity();
} catch (IllegalAccessException e) {
logger.log(Level.WARNING, "Cannot update player in PlayerEvent.", e);
}
}
// This will only occur in the NetLoginHandler injection
if (updatedPlayer != null)
currentPlayer = updatedPlayer;
}
// Make sure we're listening
if (id != null && hasListener(id)) {
// A packet has been sent guys!
PacketContainer container = new PacketContainer(id, packet);
PacketEvent event = PacketEvent.fromServer(invoker, container, currentPlayer);
invoker.invokePacketSending(event);
// Cancelling is pretty simple. Just ignore the packet.
if (event.isCancelled())
return null;
// Right, remember to replace the packet again
return event.getPacket().getHandle();
}
return packet;
}
/**
* Determine if the given injector is listening for this packet ID.
* @param packetID - packet ID to check.
* @return TRUE if it is, FALSE oterhwise.
*/
protected abstract boolean hasListener(int packetID);
/**
* Retrieve the current player's input stream.
* @param cache - whether or not to cache the result of this method.
* @return The player's input stream.
*/
public DataInputStream getInputStream(boolean cache) {
if (inputField == null)
throw new IllegalStateException("Input field is NULL.");
if (networkManager == null)
throw new IllegalStateException("Network manager is NULL.");
// Get the associated input stream
try {
if (cache && cachedInput != null)
return cachedInput;
// Save to cache
cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager, true);
return cachedInput;
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read input stream.", e);
}
}
/**
* Schedule an action to occur on the next sent packet.
* @param action - action to execute.
*/
public void scheduleAction(Runnable action) {
scheduledAction = action;
}
/**
* Retrieve the hooked player.
*/
public Player getPlayer() {
return player;
}
/**
* Retrieve the hooked player object OR the more up-to-date player instance.
* @return The hooked player, or a more up-to-date instance.
*/
public Player getUpdatedPlayer() {
if (updatedPlayer != null)
return updatedPlayer;
else
return player;
}
}
@@ -0,0 +1,342 @@
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import com.google.common.base.Objects;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
/**
* Represents an array list that wraps another list, while automatically replacing one element with another.
* <p>
* The replaced elements can be recovered.
*
* @author Kristian
* @param <TKey> - type of the elements we're replacing.
*/
class ReplacedArrayList<TKey> extends ArrayList<TKey> {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 1008492765999744804L;
private BiMap<TKey, TKey> replaceMap = HashBiMap.create();
private List<TKey> underlyingList;
public ReplacedArrayList(List<TKey> underlyingList) {
this.underlyingList = underlyingList;
}
/**
* Invoked when a element inserted is replaced.
* @param inserting - the element inserted.
* @param replacement - the element that it should replace.
*/
protected void onReplacing(TKey inserting, TKey replacement) {
// Default is to do nothing.
}
/**
* Invoked when an element is being inserted.
* <p>
* This should be used to add a "replace" map.
* @param inserting - the element to insert.
*/
protected void onInserting(TKey inserting) {
// Default is again nothing
}
/**
* Invoksed when an element is being removed.
* @param removing - the element being removed.
*/
protected void onRemoved(TKey removing) {
// Do nothing
}
@Override
public boolean add(TKey element) {
onInserting(element);
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
return delegate().add(replacement);
} else {
return delegate().add(element);
}
}
@Override
public void add(int index, TKey element) {
onInserting(element);
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
delegate().add(index, replacement);
} else {
delegate().add(index, element);
}
}
@Override
public boolean addAll(Collection<? extends TKey> collection) {
int oldSize = size();
for (TKey element : collection)
add(element);
return size() != oldSize;
}
@Override
public boolean addAll(int index, Collection<? extends TKey> elements) {
int oldSize = size();
for (TKey element : elements)
add(index++, element);
return size() != oldSize;
}
@SuppressWarnings("unchecked")
@Override
public boolean remove(Object object) {
boolean success = delegate().remove(object);
if (success)
onRemoved((TKey) object);
return success;
}
@Override
public TKey remove(int index) {
TKey removed = delegate().remove(index);
if (removed != null)
onRemoved(removed);
return removed;
}
@Override
public boolean removeAll(Collection<?> collection) {
int oldSize = size();
// Use the remove method
for (Object element : collection)
remove(element);
return size() != oldSize;
}
protected List<TKey> delegate() {
return underlyingList;
}
@Override
public void clear() {
for (TKey element : delegate())
onRemoved(element);
delegate().clear();
}
@Override
public boolean contains(Object o) {
return delegate().contains(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return delegate().containsAll(c);
}
@Override
public TKey get(int index) {
return delegate().get(index);
}
@Override
public int indexOf(Object o) {
return delegate().indexOf(o);
}
@Override
public boolean isEmpty() {
return delegate().isEmpty();
}
@Override
public Iterator<TKey> iterator() {
return delegate().iterator();
}
@Override
public int lastIndexOf(Object o) {
return delegate().lastIndexOf(o);
}
@Override
public ListIterator<TKey> listIterator() {
return delegate().listIterator();
}
@Override
public ListIterator<TKey> listIterator(int index) {
return delegate().listIterator(index);
}
@Override
public boolean retainAll(Collection<?> c) {
int oldSize = size();
for (Iterator<TKey> it = delegate().iterator(); it.hasNext(); ) {
TKey current = it.next();
// Remove elements that are not in the list
if (!c.contains(current)) {
it.remove();
onRemoved(current);
}
}
return size() != oldSize;
}
@Override
public TKey set(int index, TKey element) {
// Make sure to replace the element
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
return delegate().set(index, replacement);
} else {
return delegate().set(index, element);
}
}
@Override
public int size() {
return delegate().size();
}
@Override
public List<TKey> subList(int fromIndex, int toIndex) {
return delegate().subList(fromIndex, toIndex);
}
@Override
public Object[] toArray() {
return delegate().toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return delegate().toArray(a);
}
/**
* Add a replace rule.
* <p>
* This automatically replaces every existing element.
* @param target - instance to find.
* @param replacement - instance to replace with.
*/
public synchronized void addMapping(TKey target, TKey replacement) {
addMapping(target, replacement, false);
}
/**
* Add a replace rule.
* <p>
* This automatically replaces every existing element.
* @param target - instance to find.
* @param replacement - instance to replace with.
* @param ignoreExisting - whether or not to ignore the existing elements.
*/
public synchronized void addMapping(TKey target, TKey replacement, boolean ignoreExisting) {
replaceMap.put(target, replacement);
// Replace existing elements
if (!ignoreExisting) {
replaceAll(target, replacement);
}
}
/**
* Revert the given mapping.
* @param target - the instance we replaced.
*/
public synchronized void removeMapping(TKey target) {
// Make sure the mapping exist
if (replaceMap.containsKey(target)) {
TKey replacement = replaceMap.get(target);
replaceMap.remove(target);
// Revert existing elements
replaceAll(replacement, target);
}
}
/**
* Replace all instances of the given object.
* @param find - object to find.
* @param replace - object to replace it with.
*/
public synchronized void replaceAll(TKey find, TKey replace) {
for (int i = 0; i < underlyingList.size(); i++) {
if (Objects.equal(underlyingList.get(i), find)) {
onReplacing(find, replace);
underlyingList.set(i, replace);
}
}
}
/**
* Undo all replacements.
*/
public synchronized void revertAll() {
// No need to do anything else
if (replaceMap.size() < 1)
return;
BiMap<TKey, TKey> inverse = replaceMap.inverse();
for (int i = 0; i < underlyingList.size(); i++) {
TKey replaced = underlyingList.get(i);
if (inverse.containsKey(replaced)) {
TKey original = inverse.get(replaced);
onReplacing(replaced, original);
underlyingList.set(i, original);
}
}
replaceMap.clear();
}
@Override
protected void finalize() throws Throwable {
revertAll();
super.finalize();
}
}
@@ -0,0 +1,152 @@
package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.FieldAccessException;
/**
* Create fake player instances that represents pre-authenticated clients.
*/
class TemporaryPlayerFactory {
/**
* Able to store a PlayerInjector.
* <p>
* A necessary hack.
* @author Kristian
*/
public static class InjectContainer {
private PlayerInjector injector;
public PlayerInjector getInjector() {
return injector;
}
public void setInjector(PlayerInjector injector) {
this.injector = injector;
}
}
// Helpful constructors
private final PacketConstructor chatPacket;
public TemporaryPlayerFactory() {
chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" });
}
/**
* Construct a temporary player that supports a subset of every player command.
* <p>
* Supported methods include:
* <ul>
* <li>getPlayer()</li>
* <li>getAddress()</li>
* <li>getServer()</li>
* <li>chat(String)</li>
* <li>sendMessage(String)</li>
* <li>sendMessage(String[])</li>
* <li>kickPlayer(String)</li>
* </ul>
* <p>
* Note that the player a player has not been assigned a name yet, and thus cannot be
* uniquely identified. Use the
* @param injector - the player injector used.
* @param server - the current server.
* @return A temporary player instance.
*/
public Player createTemporaryPlayer(final Server server) {
// Default implementation
Callback implementation = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String methodName = method.getName();
PlayerInjector injector = ((InjectContainer) obj).getInjector();
// Use the socket to get the address
if (methodName.equalsIgnoreCase("getName"))
return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
if (methodName.equalsIgnoreCase("getPlayer"))
return injector.getUpdatedPlayer();
if (methodName.equalsIgnoreCase("getAddress"))
return injector.getSocket().getRemoteSocketAddress();
if (methodName.equalsIgnoreCase("getServer"))
return server;
try {
// Handle send message methods
if (methodName.equalsIgnoreCase("chat") || methodName.equalsIgnoreCase("sendMessage")) {
Object argument = args[0];
// Dynamic overloading
if (argument instanceof String) {
return sendMessage(injector, (String) argument);
} else if (argument instanceof String[]) {
for (String message : (String[]) argument) {
sendMessage(injector, message);
}
return null;
}
}
} catch (InvocationTargetException e) {
throw e.getCause();
}
// Also, handle kicking
if (methodName.equalsIgnoreCase("kickPlayer")) {
injector.disconnect((String) args[0]);
return null;
}
// Ignore all other methods
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for temporary players.");
}
};
// CGLib is amazing
Enhancer ex = new Enhancer();
ex.setSuperclass(InjectContainer.class);
ex.setInterfaces(new Class[] { Player.class });
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
ex.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
// Do not override the object method or the superclass methods
if (method.getDeclaringClass().equals(Object.class) ||
method.getDeclaringClass().equals(InjectContainer.class))
return 0;
else
return 1;
}
});
return (Player) ex.create();
}
/**
* Send a message to the given client.
* @param injector - the injector representing the client.
* @param message - a message.
* @return Always NULL.
* @throws InvocationTargetException If the message couldn't be sent.
* @throws FieldAccessException If we were unable to construct the message packet.
*/
private Object sendMessage(PlayerInjector injector, String message) throws InvocationTargetException, FieldAccessException {
injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false);
return null;
}
}
@@ -1,81 +1,81 @@
/*
* 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.metrics;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.metrics.Metrics.Graph;
public class Statistics {
// Metrics
private Metrics metrics;
public Statistics(Plugin plugin) throws IOException {
metrics = new Metrics(plugin);
metrics.start();
// Determine who is using this library
addPluginUserGraph(metrics);
}
private void addPluginUserGraph(Metrics metrics) {
Graph pluginUsers = metrics.createGraph("Plugin Users");
for (Map.Entry<String, Integer> entry : getPluginUsers(ProtocolLibrary.getProtocolManager()).entrySet()) {
final int count = entry.getValue();
// Plot plugins of this type
pluginUsers.addPlotter(new Metrics.Plotter(entry.getKey()) {
@Override
public int getValue() {
return count;
}
});
}
}
// Retrieve loaded plugins
private Map<String, Integer> getPluginUsers(ProtocolManager manager) {
Map<String, Integer> users = new HashMap<String, Integer>();
for (PacketListener listener : manager.getPacketListeners()) {
String name = PacketAdapter.getPluginName(listener);
// Increment occurence
if (!users.containsKey(name)) {
users.put(name, 1);
} else {
users.put(name, users.get(name) + 1);
}
}
return users;
}
}
/*
* 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.metrics;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.metrics.Metrics.Graph;
public class Statistics {
// Metrics
private Metrics metrics;
public Statistics(Plugin plugin) throws IOException {
metrics = new Metrics(plugin);
metrics.start();
// Determine who is using this library
addPluginUserGraph(metrics);
}
private void addPluginUserGraph(Metrics metrics) {
Graph pluginUsers = metrics.createGraph("Plugin Users");
for (Map.Entry<String, Integer> entry : getPluginUsers(ProtocolLibrary.getProtocolManager()).entrySet()) {
final int count = entry.getValue();
// Plot plugins of this type
pluginUsers.addPlotter(new Metrics.Plotter(entry.getKey()) {
@Override
public int getValue() {
return count;
}
});
}
}
// Retrieve loaded plugins
private Map<String, Integer> getPluginUsers(ProtocolManager manager) {
Map<String, Integer> users = new HashMap<String, Integer>();
for (PacketListener listener : manager.getPacketListeners()) {
String name = PacketAdapter.getPluginName(listener);
// Increment occurence
if (!users.containsKey(name)) {
users.put(name, 1);
} else {
users.put(name, users.get(name) + 1);
}
}
return users;
}
}
@@ -1,30 +1,31 @@
/*
* 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;
/**
* Interface that converts generic objects into types and back.
*
* @author Kristian
* @param <TType> The specific type.
*/
public interface EquivalentConverter<TType> {
public TType getSpecific(Object generic);
public Object getGeneric(TType specific);
public Class<TType> getSpecificType();
}
/*
* 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;
/**
* Interface that converts generic objects into types and back.
*
* @author Kristian
* @param <TType> The specific type.
*/
public interface EquivalentConverter<TType> {
public TType getSpecific(Object generic);
public Object getGeneric(TType specific);
public Class<TType> getSpecificType();
}
@@ -1,51 +1,51 @@
/*
* 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;
/**
* Invoked when a field is inaccessible due to security limitations, or when it simply doesn't exist.
*
* @author Kristian
*/
public class FieldAccessException extends Exception {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 1911011681494034617L;
public FieldAccessException() {
super();
}
public FieldAccessException(String message, Throwable cause) {
super(message, cause);
}
public FieldAccessException(String message) {
super(message);
}
public FieldAccessException(Throwable cause) {
super(cause);
}
public static FieldAccessException fromFormat(String message, Object... params) {
return new FieldAccessException(String.format(message, params));
}
}
/*
* 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;
/**
* Invoked when a field is inaccessible due to security limitations, or when it simply doesn't exist.
*
* @author Kristian
*/
public class FieldAccessException extends Exception {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 1911011681494034617L;
public FieldAccessException() {
super();
}
public FieldAccessException(String message, Throwable cause) {
super(message, cause);
}
public FieldAccessException(String message) {
super(message);
}
public FieldAccessException(Throwable cause) {
super(cause);
}
public static FieldAccessException fromFormat(String message, Object... params) {
return new FieldAccessException(String.format(message, params));
}
}
@@ -1,390 +1,396 @@
/*
* 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.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
/**
* Retrieves fields and methods by signature, not just name.
*
* @author Kristian
*/
public class FuzzyReflection {
/**
* Matches a Minecraft object.
*/
public static String MINECRAFT_OBJECT = "net\\.minecraft(\\.\\w+)+";
// The class we're actually representing
private Class<?> source;
// Whether or not to lookup private members
private boolean forceAccess;
public FuzzyReflection(Class<?> source, boolean forceAccess) {
this.source = source;
this.forceAccess = forceAccess;
}
/**
* Retrieves a fuzzy reflection instance from a given class.
* @param source - the class we'll use.
* @return A fuzzy reflection instance.
*/
public static FuzzyReflection fromClass(Class<?> source) {
return fromClass(source, false);
}
/**
* Retrieves a fuzzy reflection instance from a given class.
* @param source - the class we'll use.
* @param forceAccess - whether or not to override scope restrictions.
* @return A fuzzy reflection instance.
*/
public static FuzzyReflection fromClass(Class<?> source, boolean forceAccess) {
return new FuzzyReflection(source, forceAccess);
}
/**
* Retrieves a fuzzy reflection instance from an object.
* @param reference - the object we'll use.
* @return A fuzzy reflection instance that uses the class of the given object.
*/
public static FuzzyReflection fromObject(Object reference) {
return new FuzzyReflection(reference.getClass(), false);
}
/**
* Retrieves a fuzzy reflection instance from an object.
* @param reference - the object we'll use.
* @param forceAccess - whether or not to override scope restrictions.
* @return A fuzzy reflection instance that uses the class of the given object.
*/
public static FuzzyReflection fromObject(Object reference, boolean forceAccess) {
return new FuzzyReflection(reference.getClass(), forceAccess);
}
/**
* Retrieves the underlying class.
*/
public Class<?> getSource() {
return source;
}
/**
* Retrieves a method by looking at its name.
* @param nameRegex - regular expression that will match method names.
* @return The first method that satisfies the regular expression.
* @throws RuntimeException If the method cannot be found.
*/
public Method getMethodByName(String nameRegex) {
Pattern match = Pattern.compile(nameRegex);
for (Method method : getMethods()) {
if (match.matcher(method.getName()).matches()) {
// Right - this is probably it.
return method;
}
}
throw new RuntimeException("Unable to find a method with the pattern " +
nameRegex + " in " + source.getName());
}
/**
* Retrieves a method by looking at the parameter types only.
* @param name - potential name of the method. Only used by the error mechanism.
* @param args - parameter types of the method to find.
* @return The first method that satisfies the parameter types.
*/
public Method getMethodByParameters(String name, Class<?>... args) {
// Find the correct method to call
for (Method method : getMethods()) {
if (Arrays.equals(method.getParameterTypes(), args)) {
return method;
}
}
// That sucks
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
}
/**
* Retrieves a method by looking at the parameter types and return type only.
* @param name - potential name of the method. Only used by the error mechanism.
* @param returnType - return type of the method to find.
* @param args - parameter types of the method to find.
* @return The first method that satisfies the parameter types.
*/
public Method getMethodByParameters(String name, Class<?> returnType, Class<?>[] args) {
// Find the correct method to call
List<Method> methods = getMethodListByParameters(returnType, args);
if (methods.size() > 0) {
return methods.get(0);
} else {
// That sucks
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
}
}
/**
* Retrieves a method by looking at the parameter types and return type only.
* @param name - potential name of the method. Only used by the error mechanism.
* @param returnType - regular expression matching the return type of the method to find.
* @param args - regular expressions of the matching parameter types.
* @return The first method that satisfies the parameter types.
*/
public Method getMethodByParameters(String name, String returnTypeRegex, String[] argsRegex) {
Pattern match = Pattern.compile(returnTypeRegex);
Pattern[] argMatch = new Pattern[argsRegex.length];
for (int i = 0; i < argsRegex.length; i++) {
argMatch[i] = Pattern.compile(argsRegex[i]);
}
// Find the correct method to call
for (Method method : getMethods()) {
if (match.matcher(method.getReturnType().getName()).matches()) {
if (matchParameters(argMatch, method.getParameterTypes()))
return method;
}
}
// That sucks
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
}
private boolean matchParameters(Pattern[] parameterMatchers, Class<?>[] argTypes) {
if (parameterMatchers.length != argTypes.length)
throw new IllegalArgumentException("Arrays must have the same cardinality.");
// Check types against the regular expressions
for (int i = 0; i < argTypes.length; i++) {
if (!parameterMatchers[i].matcher(argTypes[i].getName()).matches())
return false;
}
return true;
}
/**
* Retrieves every method that has the given parameter types and return type.
* @param returnType - return type of the method to find.
* @param args - parameter types of the method to find.
* @return Every method that satisfies the given constraints.
*/
public List<Method> getMethodListByParameters(Class<?> returnType, Class<?>[] args) {
List<Method> methods = new ArrayList<Method>();
// Find the correct method to call
for (Method method : getMethods()) {
if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) {
methods.add(method);
}
}
return methods;
}
/**
* Retrieves a field by name.
* @param nameRegex - regular expression that will match a field name.
* @return The first field to match the given expression.
*/
public Field getFieldByName(String nameRegex) {
Pattern match = Pattern.compile(nameRegex);
for (Field field : getFields()) {
if (match.matcher(field.getName()).matches()) {
// Right - this is probably it.
return field;
}
}
// Looks like we're outdated. Too bad.
throw new RuntimeException("Unable to find a field with the pattern " +
nameRegex + " in " + source.getName());
}
/**
* Retrieves the first field with a type equal to or more specific to the given type.
* @param name - name the field probably is given. This will only be used in the error message.
* @param type - type of the field to find.
* @return The first field with a type that is an instance of the given type.
*/
public Field getFieldByType(String name, Class<?> type) {
List<Field> fields = getFieldListByType(type);
if (fields.size() > 0) {
return fields.get(0);
} else {
// Looks like we're outdated. Too bad.
throw new RuntimeException(String.format("Unable to find a field %s with the type %s in %s",
name, type.getName(), source.getName())
);
}
}
/**
* Retrieves every field with a type equal to or more specific to the given type.
* @param type - type of the fields to find.
* @return Every field with a type that is an instance of the given type.
*/
public List<Field> getFieldListByType(Class<?> type) {
List<Field> fields = new ArrayList<Field>();
// Field with a compatible type
for (Field field : getFields()) {
// A assignable from B -> B instanceOf A
if (type.isAssignableFrom(field.getType())) {
fields.add(field);
}
}
return fields;
}
/**
* Retrieves a field by type.
* <p>
* Note that the type is matched using the full canonical representation, i.e.:
* <ul>
* <li>java.util.List</li>
* <li>net.comphenix.xp.ExperienceMod</li>
* </ul>
* @param typeRegex - regular expression that will match the field type.
* @return The first field with a type that matches the given regular expression.
*/
public Field getFieldByType(String typeRegex) {
Pattern match = Pattern.compile(typeRegex);
// Like above, only here we test the field type
for (Field field : getFields()) {
String name = field.getType().getName();
if (match.matcher(name).matches()) {
return field;
}
}
// Looks like we're outdated. Too bad.
throw new RuntimeException("Unable to find a field with the type " +
typeRegex + " in " + source.getName());
}
/**
* Retrieves a field by type.
* <p>
* Note that the type is matched using the full canonical representation, i.e.:
* <ul>
* <li>java.util.List</li>
* <li>net.comphenix.xp.ExperienceMod</li>
* </ul>
* @param typeRegex - regular expression that will match the field type.
* @param ignored - types to ignore.
* @return The first field with a type that matches the given regular expression.
*/
@SuppressWarnings("rawtypes")
public Field getFieldByType(String typeRegex, Set<Class> ignored) {
Pattern match = Pattern.compile(typeRegex);
// Like above, only here we test the field type
for (Field field : getFields()) {
Class type = field.getType();
if (!ignored.contains(type) && match.matcher(type.getName()).matches()) {
return field;
}
}
// Looks like we're outdated. Too bad.
throw new RuntimeException("Unable to find a field with the type " +
typeRegex + " in " + source.getName());
}
/**
* Retrieves all private and public fields in declared order (after JDK 1.5).
* @return Every field.
*/
public Set<Field> getFields() {
// We will only consider private fields in the declared class
if (forceAccess)
return setUnion(source.getDeclaredFields(), source.getFields());
else
return setUnion(source.getFields());
}
/**
* Retrieves all private and public methods in declared order (after JDK 1.5).
* @return Every method.
*/
public Set<Method> getMethods() {
// We will only consider private methods in the declared class
if (forceAccess)
return setUnion(source.getDeclaredMethods(), source.getMethods());
else
return setUnion(source.getMethods());
}
// Prevent duplicate fields
private static <T> Set<T> setUnion(T[]... array) {
Set<T> result = new LinkedHashSet<T>();
for (T[] elements : array) {
for (T element : elements) {
result.add(element);
}
}
return result;
}
/**
* Retrieves whether or not not to override any scope restrictions.
* @return TRUE if we override scope, FALSE otherwise.
*/
public boolean isForceAccess() {
return forceAccess;
}
/**
* Sets whether or not not to override any scope restrictions.
* @param forceAccess - TRUE if we override scope, FALSE otherwise.
*/
public void setForceAccess(boolean forceAccess) {
this.forceAccess = forceAccess;
}
}
/*
* 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.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
/**
* Retrieves fields and methods by signature, not just name.
*
* @author Kristian
*/
public class FuzzyReflection {
/**
* Matches a Minecraft object.
*/
public static final String MINECRAFT_OBJECT = "net\\.minecraft(\\.\\w+)+";
// The class we're actually representing
private Class<?> source;
// Whether or not to lookup private members
private boolean forceAccess;
public FuzzyReflection(Class<?> source, boolean forceAccess) {
this.source = source;
this.forceAccess = forceAccess;
}
/**
* Retrieves a fuzzy reflection instance from a given class.
* @param source - the class we'll use.
* @return A fuzzy reflection instance.
*/
public static FuzzyReflection fromClass(Class<?> source) {
return fromClass(source, false);
}
/**
* Retrieves a fuzzy reflection instance from a given class.
* @param source - the class we'll use.
* @param forceAccess - whether or not to override scope restrictions.
* @return A fuzzy reflection instance.
*/
public static FuzzyReflection fromClass(Class<?> source, boolean forceAccess) {
return new FuzzyReflection(source, forceAccess);
}
/**
* Retrieves a fuzzy reflection instance from an object.
* @param reference - the object we'll use.
* @return A fuzzy reflection instance that uses the class of the given object.
*/
public static FuzzyReflection fromObject(Object reference) {
return new FuzzyReflection(reference.getClass(), false);
}
/**
* Retrieves a fuzzy reflection instance from an object.
* @param reference - the object we'll use.
* @param forceAccess - whether or not to override scope restrictions.
* @return A fuzzy reflection instance that uses the class of the given object.
*/
public static FuzzyReflection fromObject(Object reference, boolean forceAccess) {
return new FuzzyReflection(reference.getClass(), forceAccess);
}
/**
* Retrieves the underlying class.
*/
public Class<?> getSource() {
return source;
}
/**
* Retrieves a method by looking at its name.
* @param nameRegex - regular expression that will match method names.
* @return The first method that satisfies the regular expression.
* @throws IllegalArgumentException If the method cannot be found.
*/
public Method getMethodByName(String nameRegex) {
Pattern match = Pattern.compile(nameRegex);
for (Method method : getMethods()) {
if (match.matcher(method.getName()).matches()) {
// Right - this is probably it.
return method;
}
}
throw new IllegalArgumentException("Unable to find a method with the pattern " +
nameRegex + " in " + source.getName());
}
/**
* Retrieves a method by looking at the parameter types only.
* @param name - potential name of the method. Only used by the error mechanism.
* @param args - parameter types of the method to find.
* @return The first method that satisfies the parameter types.
* @throws IllegalArgumentException If the method cannot be found.
*/
public Method getMethodByParameters(String name, Class<?>... args) {
// Find the correct method to call
for (Method method : getMethods()) {
if (Arrays.equals(method.getParameterTypes(), args)) {
return method;
}
}
// That sucks
throw new IllegalArgumentException("Unable to find " + name + " in " + source.getName());
}
/**
* Retrieves a method by looking at the parameter types and return type only.
* @param name - potential name of the method. Only used by the error mechanism.
* @param returnType - return type of the method to find.
* @param args - parameter types of the method to find.
* @return The first method that satisfies the parameter types.
* @throws IllegalArgumentException If the method cannot be found.
*/
public Method getMethodByParameters(String name, Class<?> returnType, Class<?>[] args) {
// Find the correct method to call
List<Method> methods = getMethodListByParameters(returnType, args);
if (methods.size() > 0) {
return methods.get(0);
} else {
// That sucks
throw new IllegalArgumentException("Unable to find " + name + " in " + source.getName());
}
}
/**
* Retrieves a method by looking at the parameter types and return type only.
* @param name - potential name of the method. Only used by the error mechanism.
* @param returnTypeRegex - regular expression matching the return type of the method to find.
* @param argsRegex - regular expressions of the matching parameter types.
* @return The first method that satisfies the parameter types.
* @throws IllegalArgumentException If the method cannot be found.
*/
public Method getMethodByParameters(String name, String returnTypeRegex, String[] argsRegex) {
Pattern match = Pattern.compile(returnTypeRegex);
Pattern[] argMatch = new Pattern[argsRegex.length];
for (int i = 0; i < argsRegex.length; i++) {
argMatch[i] = Pattern.compile(argsRegex[i]);
}
// Find the correct method to call
for (Method method : getMethods()) {
if (match.matcher(method.getReturnType().getName()).matches()) {
if (matchParameters(argMatch, method.getParameterTypes()))
return method;
}
}
// That sucks
throw new IllegalArgumentException("Unable to find " + name + " in " + source.getName());
}
private boolean matchParameters(Pattern[] parameterMatchers, Class<?>[] argTypes) {
if (parameterMatchers.length != argTypes.length)
throw new IllegalArgumentException("Arrays must have the same cardinality.");
// Check types against the regular expressions
for (int i = 0; i < argTypes.length; i++) {
if (!parameterMatchers[i].matcher(argTypes[i].getName()).matches())
return false;
}
return true;
}
/**
* Retrieves every method that has the given parameter types and return type.
* @param returnType - return type of the method to find.
* @param args - parameter types of the method to find.
* @return Every method that satisfies the given constraints.
*/
public List<Method> getMethodListByParameters(Class<?> returnType, Class<?>[] args) {
List<Method> methods = new ArrayList<Method>();
// Find the correct method to call
for (Method method : getMethods()) {
if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) {
methods.add(method);
}
}
return methods;
}
/**
* Retrieves a field by name.
* @param nameRegex - regular expression that will match a field name.
* @return The first field to match the given expression.
* @throws IllegalArgumentException If the field cannot be found.
*/
public Field getFieldByName(String nameRegex) {
Pattern match = Pattern.compile(nameRegex);
for (Field field : getFields()) {
if (match.matcher(field.getName()).matches()) {
// Right - this is probably it.
return field;
}
}
// Looks like we're outdated. Too bad.
throw new IllegalArgumentException("Unable to find a field with the pattern " +
nameRegex + " in " + source.getName());
}
/**
* Retrieves the first field with a type equal to or more specific to the given type.
* @param name - name the field probably is given. This will only be used in the error message.
* @param type - type of the field to find.
* @return The first field with a type that is an instance of the given type.
*/
public Field getFieldByType(String name, Class<?> type) {
List<Field> fields = getFieldListByType(type);
if (fields.size() > 0) {
return fields.get(0);
} else {
// Looks like we're outdated. Too bad.
throw new IllegalArgumentException(String.format("Unable to find a field %s with the type %s in %s",
name, type.getName(), source.getName())
);
}
}
/**
* Retrieves every field with a type equal to or more specific to the given type.
* @param type - type of the fields to find.
* @return Every field with a type that is an instance of the given type.
*/
public List<Field> getFieldListByType(Class<?> type) {
List<Field> fields = new ArrayList<Field>();
// Field with a compatible type
for (Field field : getFields()) {
// A assignable from B -> B instanceOf A
if (type.isAssignableFrom(field.getType())) {
fields.add(field);
}
}
return fields;
}
/**
* Retrieves a field by type.
* <p>
* Note that the type is matched using the full canonical representation, i.e.:
* <ul>
* <li>java.util.List</li>
* <li>net.comphenix.xp.ExperienceMod</li>
* </ul>
* @param typeRegex - regular expression that will match the field type.
* @return The first field with a type that matches the given regular expression.
* @throws IllegalArgumentException If the field cannot be found.
*/
public Field getFieldByType(String typeRegex) {
Pattern match = Pattern.compile(typeRegex);
// Like above, only here we test the field type
for (Field field : getFields()) {
String name = field.getType().getName();
if (match.matcher(name).matches()) {
return field;
}
}
// Looks like we're outdated. Too bad.
throw new IllegalArgumentException("Unable to find a field with the type " +
typeRegex + " in " + source.getName());
}
/**
* Retrieves a field by type.
* <p>
* Note that the type is matched using the full canonical representation, i.e.:
* <ul>
* <li>java.util.List</li>
* <li>net.comphenix.xp.ExperienceMod</li>
* </ul>
* @param typeRegex - regular expression that will match the field type.
* @param ignored - types to ignore.
* @return The first field with a type that matches the given regular expression.
* @throws IllegalArgumentException If the field cannot be found.
*/
@SuppressWarnings("rawtypes")
public Field getFieldByType(String typeRegex, Set<Class> ignored) {
Pattern match = Pattern.compile(typeRegex);
// Like above, only here we test the field type
for (Field field : getFields()) {
Class type = field.getType();
if (!ignored.contains(type) && match.matcher(type.getName()).matches()) {
return field;
}
}
// Looks like we're outdated. Too bad.
throw new IllegalArgumentException("Unable to find a field with the type " +
typeRegex + " in " + source.getName());
}
/**
* Retrieves all private and public fields in declared order (after JDK 1.5).
* @return Every field.
*/
public Set<Field> getFields() {
// We will only consider private fields in the declared class
if (forceAccess)
return setUnion(source.getDeclaredFields(), source.getFields());
else
return setUnion(source.getFields());
}
/**
* Retrieves all private and public methods in declared order (after JDK 1.5).
* @return Every method.
*/
public Set<Method> getMethods() {
// We will only consider private methods in the declared class
if (forceAccess)
return setUnion(source.getDeclaredMethods(), source.getMethods());
else
return setUnion(source.getMethods());
}
// Prevent duplicate fields
private static <T> Set<T> setUnion(T[]... array) {
Set<T> result = new LinkedHashSet<T>();
for (T[] elements : array) {
for (T element : elements) {
result.add(element);
}
}
return result;
}
/**
* Retrieves whether or not not to override any scope restrictions.
* @return TRUE if we override scope, FALSE otherwise.
*/
public boolean isForceAccess() {
return forceAccess;
}
/**
* Sets whether or not not to override any scope restrictions.
* @param forceAccess - TRUE if we override scope, FALSE otherwise.
*/
public void setForceAccess(boolean forceAccess) {
this.forceAccess = forceAccess;
}
}
@@ -1,79 +1,105 @@
package com.comphenix.protocol.reflect;
import java.lang.reflect.Field;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
/**
* Represents a traditional int field enum.
*
* @author Kristian
*/
public class IntEnum {
// Used to convert between IDs and names
protected BiMap<Integer, String> members = HashBiMap.create();
/**
* Registers every declared integer field.
*/
public IntEnum() {
registerAll();
}
/**
* Registers every public int field as a member.
*/
protected void registerAll() {
try {
// Register every int field
for (Field entry : this.getClass().getFields()) {
if (entry.getType().equals(int.class)) {
registerMember(entry.getInt(this), entry.getName());
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* Registers a member.
* @param id - id of member.
* @param name - name of member.
*/
protected void registerMember(int id, String name) {
members.put(id, name);
}
/**
* Determines whether or not the given member exists.
* @param id - the ID of the member to find.
* @return TRUE if a member with the given ID exists, FALSE otherwise.
*/
public boolean hasMember(int id) {
return members.containsKey(id);
}
/**
* Retrieve the ID of the member with the given name.
* @param name - name of member to retrieve.
* @return ID of the member, or NULL if not found.
*/
public Integer valueOf(String name) {
return members.inverse().get(name);
}
/**
* Retrieve the name of the member with the given id.
* @param id - id of the member to retrieve.
* @return Declared name of the member, or NULL if not found.
*/
public String getDeclaredName(Integer id) {
return members.get(id);
}
}
/*
* 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.Field;
import java.util.Set;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
/**
* Represents a traditional int field enum.
*
* @author Kristian
*/
public class IntEnum {
// Used to convert between IDs and names
protected BiMap<Integer, String> members = HashBiMap.create();
/**
* Registers every declared integer field.
*/
public IntEnum() {
registerAll();
}
/**
* Registers every public int field as a member.
*/
protected void registerAll() {
try {
// Register every int field
for (Field entry : this.getClass().getFields()) {
if (entry.getType().equals(int.class)) {
registerMember(entry.getInt(this), entry.getName());
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* Registers a member.
* @param id - id of member.
* @param name - name of member.
*/
protected void registerMember(int id, String name) {
members.put(id, name);
}
/**
* Determines whether or not the given member exists.
* @param id - the ID of the member to find.
* @return TRUE if a member with the given ID exists, FALSE otherwise.
*/
public boolean hasMember(int id) {
return members.containsKey(id);
}
/**
* Retrieve the ID of the member with the given name.
* @param name - name of member to retrieve.
* @return ID of the member, or NULL if not found.
*/
public Integer valueOf(String name) {
return members.inverse().get(name);
}
/**
* Retrieve the name of the member with the given id.
* @param id - id of the member to retrieve.
* @return Declared name of the member, or NULL if not found.
*/
public String getDeclaredName(Integer id) {
return members.get(id);
}
/**
* Retrieve the ID of every registered member.
* @return Enumeration of every value.
*/
public Set<Integer> values() {
return members.keySet();
}
}
@@ -1,70 +1,87 @@
package com.comphenix.protocol.reflect;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Can copy an object field by field.
*
* @author Kristian
*/
public class ObjectCloner {
// 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.
* <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) {
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;
}
// Add target
StructureModifier<Object> modifierSource = modifier.withTarget(source);
StructureModifier<Object> modifierDest = modifier.withTarget(destination);
// Copy every field
try {
for (int i = 0; i < modifierSource.size(); i++) {
Object value = modifierSource.read(i);
modifierDest.write(i, value);
// System.out.println(String.format("Writing value %s to %s",
// value, modifier.getFields().get(i).getName()));
}
// Copy private fields underneath
Class<?> superclass = commonType.getSuperclass();
if (!superclass.equals(Object.class)) {
copyTo(source, destination, superclass);
}
} catch (FieldAccessException e) {
throw new RuntimeException("Unable to copy fields from " + commonType.getName(), e);
}
}
}
/*
* 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.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Can copy an object field by field.
*
* @author Kristian
*/
public class ObjectCloner {
// 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.
* <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) {
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;
}
// Add target
StructureModifier<Object> modifierSource = modifier.withTarget(source);
StructureModifier<Object> modifierDest = modifier.withTarget(destination);
// Copy every field
try {
for (int i = 0; i < modifierSource.size(); i++) {
Object value = modifierSource.read(i);
modifierDest.write(i, value);
// System.out.println(String.format("Writing value %s to %s",
// value, modifier.getFields().get(i).getName()));
}
// Copy private fields underneath
Class<?> superclass = commonType.getSuperclass();
if (!superclass.equals(Object.class)) {
copyTo(source, destination, superclass);
}
} catch (FieldAccessException e) {
throw new RuntimeException("Unable to copy fields from " + commonType.getName(), e);
}
}
}
@@ -1,124 +1,124 @@
package com.comphenix.protocol.reflect;
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Contains static utility methods pertaining to primitive types and their
* corresponding wrapper types.
*
* @author Kevin Bourrillion
*/
public final class PrimitiveUtils {
private PrimitiveUtils() {
}
/** A map from primitive types to their corresponding wrapper types. */
private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER_TYPE;
/** A map from wrapper types to their corresponding primitive types. */
private static final Map<Class<?>, Class<?>> WRAPPER_TO_PRIMITIVE_TYPE;
// Sad that we can't use a BiMap. :(
static {
Map<Class<?>, Class<?>> primToWrap = new HashMap<Class<?>, Class<?>>(16);
Map<Class<?>, Class<?>> wrapToPrim = new HashMap<Class<?>, Class<?>>(16);
add(primToWrap, wrapToPrim, boolean.class, Boolean.class);
add(primToWrap, wrapToPrim, byte.class, Byte.class);
add(primToWrap, wrapToPrim, char.class, Character.class);
add(primToWrap, wrapToPrim, double.class, Double.class);
add(primToWrap, wrapToPrim, float.class, Float.class);
add(primToWrap, wrapToPrim, int.class, Integer.class);
add(primToWrap, wrapToPrim, long.class, Long.class);
add(primToWrap, wrapToPrim, short.class, Short.class);
add(primToWrap, wrapToPrim, void.class, Void.class);
PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap);
WRAPPER_TO_PRIMITIVE_TYPE = Collections.unmodifiableMap(wrapToPrim);
}
private static void add(Map<Class<?>, Class<?>> forward,
Map<Class<?>, Class<?>> backward, Class<?> key, Class<?> value) {
forward.put(key, value);
backward.put(value, key);
}
/**
* Returns true if this type is a primitive.
*/
public static boolean isPrimitive(Type type) {
return PRIMITIVE_TO_WRAPPER_TYPE.containsKey(type);
}
/**
* Returns {@code true} if {@code type} is one of the nine primitive-wrapper
* types, such as {@link Integer}.
*
* @see Class#isPrimitive
*/
public static boolean isWrapperType(Type type) {
return WRAPPER_TO_PRIMITIVE_TYPE.containsKey(checkNotNull(type));
}
/**
* Returns the corresponding wrapper type of {@code type} if it is a
* primitive type; otherwise returns {@code type} itself. Idempotent.
*
* <pre>
* wrap(int.class) == Integer.class
* wrap(Integer.class) == Integer.class
* wrap(String.class) == String.class
* </pre>
*/
public static <T> Class<T> wrap(Class<T> type) {
// cast is safe: long.class and Long.class are both of type Class<Long>
@SuppressWarnings("unchecked")
Class<T> wrapped = (Class<T>) PRIMITIVE_TO_WRAPPER_TYPE
.get(checkNotNull(type));
return (wrapped == null) ? type : wrapped;
}
/**
* Returns the corresponding primitive type of {@code type} if it is a
* wrapper type; otherwise returns {@code type} itself. Idempotent.
*
* <pre>
* unwrap(Integer.class) == int.class
* unwrap(int.class) == int.class
* unwrap(String.class) == String.class
* </pre>
*/
public static <T> Class<T> unwrap(Class<T> type) {
// cast is safe: long.class and Long.class are both of type Class<Long>
@SuppressWarnings("unchecked")
Class<T> unwrapped = (Class<T>) WRAPPER_TO_PRIMITIVE_TYPE
.get(checkNotNull(type));
return (unwrapped == null) ? type : unwrapped;
}
public static <T> T checkNotNull(T obj) {
if (obj == null) {
throw new NullPointerException();
}
return obj;
}
}
package com.comphenix.protocol.reflect;
/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Contains static utility methods pertaining to primitive types and their
* corresponding wrapper types.
*
* @author Kevin Bourrillion
*/
public final class PrimitiveUtils {
private PrimitiveUtils() {
}
/** A map from primitive types to their corresponding wrapper types. */
private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER_TYPE;
/** A map from wrapper types to their corresponding primitive types. */
private static final Map<Class<?>, Class<?>> WRAPPER_TO_PRIMITIVE_TYPE;
// Sad that we can't use a BiMap. :(
static {
Map<Class<?>, Class<?>> primToWrap = new HashMap<Class<?>, Class<?>>(16);
Map<Class<?>, Class<?>> wrapToPrim = new HashMap<Class<?>, Class<?>>(16);
add(primToWrap, wrapToPrim, boolean.class, Boolean.class);
add(primToWrap, wrapToPrim, byte.class, Byte.class);
add(primToWrap, wrapToPrim, char.class, Character.class);
add(primToWrap, wrapToPrim, double.class, Double.class);
add(primToWrap, wrapToPrim, float.class, Float.class);
add(primToWrap, wrapToPrim, int.class, Integer.class);
add(primToWrap, wrapToPrim, long.class, Long.class);
add(primToWrap, wrapToPrim, short.class, Short.class);
add(primToWrap, wrapToPrim, void.class, Void.class);
PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap);
WRAPPER_TO_PRIMITIVE_TYPE = Collections.unmodifiableMap(wrapToPrim);
}
private static void add(Map<Class<?>, Class<?>> forward,
Map<Class<?>, Class<?>> backward, Class<?> key, Class<?> value) {
forward.put(key, value);
backward.put(value, key);
}
/**
* Returns true if this type is a primitive.
*/
public static boolean isPrimitive(Type type) {
return PRIMITIVE_TO_WRAPPER_TYPE.containsKey(type);
}
/**
* Returns {@code true} if {@code type} is one of the nine primitive-wrapper
* types, such as {@link Integer}.
*
* @see Class#isPrimitive
*/
public static boolean isWrapperType(Type type) {
return WRAPPER_TO_PRIMITIVE_TYPE.containsKey(checkNotNull(type));
}
/**
* Returns the corresponding wrapper type of {@code type} if it is a
* primitive type; otherwise returns {@code type} itself. Idempotent.
*
* <pre>
* wrap(int.class) == Integer.class
* wrap(Integer.class) == Integer.class
* wrap(String.class) == String.class
* </pre>
*/
public static <T> Class<T> wrap(Class<T> type) {
// cast is safe: long.class and Long.class are both of type Class<Long>
@SuppressWarnings("unchecked")
Class<T> wrapped = (Class<T>) PRIMITIVE_TO_WRAPPER_TYPE
.get(checkNotNull(type));
return (wrapped == null) ? type : wrapped;
}
/**
* Returns the corresponding primitive type of {@code type} if it is a
* wrapper type; otherwise returns {@code type} itself. Idempotent.
*
* <pre>
* unwrap(Integer.class) == int.class
* unwrap(int.class) == int.class
* unwrap(String.class) == String.class
* </pre>
*/
public static <T> Class<T> unwrap(Class<T> type) {
// cast is safe: long.class and Long.class are both of type Class<Long>
@SuppressWarnings("unchecked")
Class<T> unwrapped = (Class<T>) WRAPPER_TO_PRIMITIVE_TYPE
.get(checkNotNull(type));
return (unwrapped == null) ? type : unwrapped;
}
public static <T> T checkNotNull(T obj) {
if (obj == null) {
throw new NullPointerException();
}
return obj;
}
}
@@ -1,463 +1,463 @@
/*
* 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.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
/**
* Provides list-oriented access to the fields of a Minecraft packet.
* <p>
* Implemented by using reflection. Use a CompiledStructureModifier, if speed is essential.
*
* @author Kristian
* @param <TField> Type of the fields to retrieve.
*/
@SuppressWarnings("rawtypes")
public class StructureModifier<TField> {
// Object and its type
protected Class targetType;
protected Object target;
// Converter. May be NULL.
protected EquivalentConverter<TField> converter;
// The fields to read in order
protected Class fieldType;
protected List<Field> data = new ArrayList<Field>();
// Improved default values
protected Map<Field, Integer> defaultFields;
// Cache of previous types
protected Map<Class, StructureModifier> subtypeCache;
// Whether or subclasses should handle conversion
protected boolean customConvertHandling;
/**
* Creates a structure modifier.
* @param targetType - the structure to modify.
* @param superclassExclude - a superclass to exclude.
* @param requireDefault - whether or not we will be using writeDefaults().
*/
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
List<Field> fields = getFields(targetType, superclassExclude);
Map<Field, Integer> defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<Field, Integer>();
initialize(targetType, Object.class, fields, defaults, null, new ConcurrentHashMap<Class, StructureModifier>());
}
/**
* Consumers of this method should call "initialize".
*/
protected StructureModifier() {
}
/**
* Initialize using the same field types.
* @param other - information to set.
*/
protected void initialize(StructureModifier<TField> other) {
initialize(other.targetType, other.fieldType, other.data,
other.defaultFields, other.converter, other.subtypeCache);
}
/**
* Initialize every field of this class.
* @param targetType - type of the object we're reading and writing from.
* @param fieldType - the common type of the fields we're modifying.
* @param data - list of fields to modify.
* @param defaultFields - list of fields that will be automatically initialized.
* @param converter - converts between the common field type and the actual type the consumer expects.
* @param subTypeCache - a structure modifier cache.
*/
protected void initialize(Class targetType, Class fieldType,
List<Field> data, Map<Field, Integer> defaultFields,
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
this.targetType = targetType;
this.fieldType = fieldType;
this.data = data;
this.defaultFields = defaultFields;
this.converter = converter;
this.subtypeCache = subTypeCache;
}
/**
* Reads the value of a field given its index.
* @param fieldIndex - index of the field.
* @return Value of the field.
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
*/
@SuppressWarnings("unchecked")
public TField read(int fieldIndex) throws FieldAccessException {
if (fieldIndex < 0 || fieldIndex >= data.size())
throw new FieldAccessException("Field index must be within 0 - count",
new IndexOutOfBoundsException("Out of bounds"));
if (target == null)
throw new IllegalStateException("Cannot read from a NULL target.");
try {
Object result = FieldUtils.readField(data.get(fieldIndex), target, true);
// Use the converter, if we have it
if (needConversion())
return converter.getSpecific(result);
else
return (TField) result;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot read field due to a security limitation.", e);
}
}
/**
* Reads the value of a field if and ONLY IF it exists.
* @param fieldIndex - index of the field.
* @return Value of the field, or NULL if it doesn't exist.
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
*/
public TField readSafely(int fieldIndex) throws FieldAccessException {
if (fieldIndex >= 0 && fieldIndex < data.size()) {
return read(fieldIndex);
} else {
return null;
}
}
/**
* Writes the value of a field given its index.
* @param fieldIndex - index of the field.
* @param value - new value of the field.
* @return This structure modifier - for chaining.
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
*/
public StructureModifier<TField> write(int fieldIndex, TField value) throws FieldAccessException {
if (fieldIndex < 0 || fieldIndex >= data.size())
throw new FieldAccessException("Field index must be within 0 - count",
new IndexOutOfBoundsException("Out of bounds"));
if (target == null)
throw new IllegalStateException("Cannot write to a NULL target.");
// Use the converter, if it exists
Object obj = needConversion() ? converter.getGeneric(value) : value;
try {
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot read field due to a security limitation.", e);
}
// Make this method chainable
return this;
}
/**
* Whether or not we should use the converter instance.
* @return TRUE if we should, FALSE otherwise.
*/
private final boolean needConversion() {
return converter != null && !customConvertHandling;
}
/**
* Writes the value of a given field IF and ONLY if it exists.
* @param fieldIndex - index of the potential field.
* @param value - new value of the field.
* @return This structure modifer - for chaining.
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
*/
public StructureModifier<TField> writeSafely(int fieldIndex, TField value) throws FieldAccessException {
if (fieldIndex >= 0 && fieldIndex < data.size()) {
write(fieldIndex, value);
}
return this;
}
/**
* Correctly modifies the value of a field.
* @param fieldIndex - index of the field to modify.
* @param select - the function that modifies the field value.
* @return This structure modifier - for chaining.
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
*/
public StructureModifier<TField> modify(int fieldIndex, Function<TField, TField> select) throws FieldAccessException {
TField value = read(fieldIndex);
return write(fieldIndex, select.apply(value));
}
/**
* Retrieves a structure modifier that only reads and writes fields of a given type.
* @param fieldType - the type, or supertype, of every field to modify.
* @return A structure modifier for fields of this type.
*/
public <T> StructureModifier<T> withType(Class fieldType) {
return withType(fieldType, null);
}
/**
* Sets all non-primitive fields to a more fitting default value. See {@link DefaultInstances#getDefault(Class)}.
* @return The current structure modifier - for chaining.
* @throws FieldAccessException If we're unable to write to the fields due to a security limitation.
*/
public StructureModifier<TField> writeDefaults() throws FieldAccessException {
DefaultInstances generator = DefaultInstances.DEFAULT;
// Write a default instance to every field
for (Field field : defaultFields.keySet()) {
try {
FieldUtils.writeField(field, target,
generator.getDefault(field.getType()), true);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot write to field due to a security limitation.", e);
}
}
return this;
}
/**
* Retrieves a structure modifier that only reads and writes fields of a given type.
* @param fieldType - the type, or supertype, of every field to modify.
* @param converter - converts objects into the given type.
* @return A structure modifier for fields of this type.
*/
@SuppressWarnings("unchecked")
public <T> StructureModifier<T> withType(Class fieldType, EquivalentConverter<T> converter) {
StructureModifier<T> result = subtypeCache.get(fieldType);
// Do we need to update the cache?
if (result == null) {
List<Field> filtered = new ArrayList<Field>();
Map<Field, Integer> defaults = new HashMap<Field, Integer>();
int index = 0;
for (Field field : data) {
if (fieldType != null && fieldType.isAssignableFrom(field.getType())) {
filtered.add(field);
// Don't use the original index
if (defaultFields.containsKey(field))
defaults.put(field, index);
}
// Keep track of the field index
index++;
}
// Cache structure modifiers
result = withFieldType(fieldType, filtered, defaults, converter);
if (fieldType != null) {
subtypeCache.put(fieldType, result);
// Automatically compile the structure modifier
if (BackgroundCompiler.getInstance() != null)
BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType);
}
}
// Add the target too
result = result.withTarget(target);
// And the converter, if it's needed
if (!sameConverter(result.converter, converter)) {
result = result.withConverter(converter);
}
return result;
}
private boolean sameConverter(EquivalentConverter<?> a, EquivalentConverter<?> b) {
// Compare the converter types
if (a == null)
return b == null;
else if (b == null)
return a == null;
else
return a.getSpecificType().equals(b.getSpecificType());
}
/**
* Retrieves the common type of each field.
* @return Common type of each field.
*/
public Class getFieldType() {
return fieldType;
}
/**
* Retrieves the type of the object we're modifying.
* @return Type of the object.
*/
public Class getTargetType() {
return targetType;
}
/**
* Retrieves the object we're currently modifying.
* @return Object we're modifying.
*/
public Object getTarget() {
return target;
}
/**
* Retrieve the number of readable types.
* @return Readable types.
*/
public int size() {
return data.size();
}
/**
* Create a new structure modifier for the new field type.
* @param fieldType - common type of each field.
* @param filtered - list of fields after filtering the original modifier.
* @param defaults - list of default values after filtering the original.
* @param converter - the new converter.
* @return A new structure modifier.
*/
protected <T> StructureModifier<T> withFieldType(
Class fieldType, List<Field> filtered,
Map<Field, Integer> defaults, EquivalentConverter<T> converter) {
StructureModifier<T> result = new StructureModifier<T>();
result.initialize(targetType, fieldType, filtered, defaults,
converter, new ConcurrentHashMap<Class, StructureModifier>());
return result;
}
/**
* Retrieves a structure modifier of the same type for a different object target.
* @param target - different target of the same type.
* @return Structure modifier with the new target.
*/
public StructureModifier<TField> withTarget(Object target) {
StructureModifier<TField> copy = new StructureModifier<TField>();
// Create a new instance
copy.initialize(targetType, fieldType, data, defaultFields, converter, subtypeCache);
copy.target = target;
return copy;
}
/**
* Retrieves a structure modifier with the same type and target, but using a new object converter.
* @param converter - the object converter to use.
* @return Structure modifier with the new converter.
*/
@SuppressWarnings("unchecked")
private <T> StructureModifier<T> withConverter(EquivalentConverter<T> converter) {
StructureModifier copy = withTarget(target);
copy.setConverter(converter);
return copy;
}
/**
* Set the current object converter. Should only be called during construction.
* @param converter - current object converter.
*/
protected void setConverter(EquivalentConverter<TField> converter) {
this.converter = converter;
}
/**
* Retrieves a list of the fields matching the constraints of this structure modifier.
* @return List of fields.
*/
public List<Field> getFields() {
return ImmutableList.copyOf(data);
}
/**
* Retrieve every value stored in the fields of the current type.
* @return Every field value.
* @throws FieldAccessException Unable to access one or all of the fields
*/
public List<TField> getValues() throws FieldAccessException {
List<TField> values = new ArrayList<TField>();
for (int i = 0; i < size(); i++) {
values.add(read(i));
}
return values;
}
// Used to generate plausible default values
private static Map<Field, Integer> generateDefaultFields(List<Field> fields) {
Map<Field, Integer> requireDefaults = new HashMap<Field, Integer>();
DefaultInstances generator = DefaultInstances.DEFAULT;
int index = 0;
for (Field field : fields) {
Class<?> type = field.getType();
// First, ignore primitive fields
if (!PrimitiveUtils.isPrimitive(type)) {
// Next, see if we actually can generate a default value
if (generator.getDefault(type) != null) {
// If so, require it
requireDefaults.put(field, index);
}
}
// Increment field index
index++;
}
return requireDefaults;
}
// Used to filter out irrelevant fields
private static List<Field> getFields(Class type, Class superclassExclude) {
List<Field> result = new ArrayList<Field>();
// Retrieve every private and public field
for (Field field : FuzzyReflection.fromClass(type, true).getFields()) {
int mod = field.getModifiers();
// Ignore static, final and "abstract packet" fields
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && (
superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude)
)) {
result.add(field);
}
}
return result;
}
}
/*
* 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.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
/**
* Provides list-oriented access to the fields of a Minecraft packet.
* <p>
* Implemented by using reflection. Use a CompiledStructureModifier, if speed is essential.
*
* @author Kristian
* @param <TField> Type of the fields to retrieve.
*/
@SuppressWarnings("rawtypes")
public class StructureModifier<TField> {
// Object and its type
protected Class targetType;
protected Object target;
// Converter. May be NULL.
protected EquivalentConverter<TField> converter;
// The fields to read in order
protected Class fieldType;
protected List<Field> data = new ArrayList<Field>();
// Improved default values
protected Map<Field, Integer> defaultFields;
// Cache of previous types
protected Map<Class, StructureModifier> subtypeCache;
// Whether or subclasses should handle conversion
protected boolean customConvertHandling;
/**
* Creates a structure modifier.
* @param targetType - the structure to modify.
* @param superclassExclude - a superclass to exclude.
* @param requireDefault - whether or not we will be using writeDefaults().
*/
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
List<Field> fields = getFields(targetType, superclassExclude);
Map<Field, Integer> defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<Field, Integer>();
initialize(targetType, Object.class, fields, defaults, null, new ConcurrentHashMap<Class, StructureModifier>());
}
/**
* Consumers of this method should call "initialize".
*/
protected StructureModifier() {
}
/**
* Initialize using the same field types.
* @param other - information to set.
*/
protected void initialize(StructureModifier<TField> other) {
initialize(other.targetType, other.fieldType, other.data,
other.defaultFields, other.converter, other.subtypeCache);
}
/**
* Initialize every field of this class.
* @param targetType - type of the object we're reading and writing from.
* @param fieldType - the common type of the fields we're modifying.
* @param data - list of fields to modify.
* @param defaultFields - list of fields that will be automatically initialized.
* @param converter - converts between the common field type and the actual type the consumer expects.
* @param subTypeCache - a structure modifier cache.
*/
protected void initialize(Class targetType, Class fieldType,
List<Field> data, Map<Field, Integer> defaultFields,
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
this.targetType = targetType;
this.fieldType = fieldType;
this.data = data;
this.defaultFields = defaultFields;
this.converter = converter;
this.subtypeCache = subTypeCache;
}
/**
* Reads the value of a field given its index.
* @param fieldIndex - index of the field.
* @return Value of the field.
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
*/
@SuppressWarnings("unchecked")
public TField read(int fieldIndex) throws FieldAccessException {
if (fieldIndex < 0 || fieldIndex >= data.size())
throw new FieldAccessException("Field index must be within 0 - count",
new IndexOutOfBoundsException("Out of bounds"));
if (target == null)
throw new IllegalStateException("Cannot read from a NULL target.");
try {
Object result = FieldUtils.readField(data.get(fieldIndex), target, true);
// Use the converter, if we have it
if (needConversion())
return converter.getSpecific(result);
else
return (TField) result;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot read field due to a security limitation.", e);
}
}
/**
* Reads the value of a field if and ONLY IF it exists.
* @param fieldIndex - index of the field.
* @return Value of the field, or NULL if it doesn't exist.
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
*/
public TField readSafely(int fieldIndex) throws FieldAccessException {
if (fieldIndex >= 0 && fieldIndex < data.size()) {
return read(fieldIndex);
} else {
return null;
}
}
/**
* Writes the value of a field given its index.
* @param fieldIndex - index of the field.
* @param value - new value of the field.
* @return This structure modifier - for chaining.
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
*/
public StructureModifier<TField> write(int fieldIndex, TField value) throws FieldAccessException {
if (fieldIndex < 0 || fieldIndex >= data.size())
throw new FieldAccessException("Field index must be within 0 - count",
new IndexOutOfBoundsException("Out of bounds"));
if (target == null)
throw new IllegalStateException("Cannot write to a NULL target.");
// Use the converter, if it exists
Object obj = needConversion() ? converter.getGeneric(value) : value;
try {
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot read field due to a security limitation.", e);
}
// Make this method chainable
return this;
}
/**
* Whether or not we should use the converter instance.
* @return TRUE if we should, FALSE otherwise.
*/
private final boolean needConversion() {
return converter != null && !customConvertHandling;
}
/**
* Writes the value of a given field IF and ONLY if it exists.
* @param fieldIndex - index of the potential field.
* @param value - new value of the field.
* @return This structure modifer - for chaining.
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
*/
public StructureModifier<TField> writeSafely(int fieldIndex, TField value) throws FieldAccessException {
if (fieldIndex >= 0 && fieldIndex < data.size()) {
write(fieldIndex, value);
}
return this;
}
/**
* Correctly modifies the value of a field.
* @param fieldIndex - index of the field to modify.
* @param select - the function that modifies the field value.
* @return This structure modifier - for chaining.
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
*/
public StructureModifier<TField> modify(int fieldIndex, Function<TField, TField> select) throws FieldAccessException {
TField value = read(fieldIndex);
return write(fieldIndex, select.apply(value));
}
/**
* Retrieves a structure modifier that only reads and writes fields of a given type.
* @param fieldType - the type, or supertype, of every field to modify.
* @return A structure modifier for fields of this type.
*/
public <T> StructureModifier<T> withType(Class fieldType) {
return withType(fieldType, null);
}
/**
* Sets all non-primitive fields to a more fitting default value. See {@link DefaultInstances#getDefault(Class)}.
* @return The current structure modifier - for chaining.
* @throws FieldAccessException If we're unable to write to the fields due to a security limitation.
*/
public StructureModifier<TField> writeDefaults() throws FieldAccessException {
DefaultInstances generator = DefaultInstances.DEFAULT;
// Write a default instance to every field
for (Field field : defaultFields.keySet()) {
try {
FieldUtils.writeField(field, target,
generator.getDefault(field.getType()), true);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot write to field due to a security limitation.", e);
}
}
return this;
}
/**
* Retrieves a structure modifier that only reads and writes fields of a given type.
* @param fieldType - the type, or supertype, of every field to modify.
* @param converter - converts objects into the given type.
* @return A structure modifier for fields of this type.
*/
@SuppressWarnings("unchecked")
public <T> StructureModifier<T> withType(Class fieldType, EquivalentConverter<T> converter) {
StructureModifier<T> result = subtypeCache.get(fieldType);
// Do we need to update the cache?
if (result == null) {
List<Field> filtered = new ArrayList<Field>();
Map<Field, Integer> defaults = new HashMap<Field, Integer>();
int index = 0;
for (Field field : data) {
if (fieldType != null && fieldType.isAssignableFrom(field.getType())) {
filtered.add(field);
// Don't use the original index
if (defaultFields.containsKey(field))
defaults.put(field, index);
}
// Keep track of the field index
index++;
}
// Cache structure modifiers
result = withFieldType(fieldType, filtered, defaults, converter);
if (fieldType != null) {
subtypeCache.put(fieldType, result);
// Automatically compile the structure modifier
if (BackgroundCompiler.getInstance() != null)
BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType);
}
}
// Add the target too
result = result.withTarget(target);
// And the converter, if it's needed
if (!sameConverter(result.converter, converter)) {
result = result.withConverter(converter);
}
return result;
}
private boolean sameConverter(EquivalentConverter<?> a, EquivalentConverter<?> b) {
// Compare the converter types
if (a == null)
return b == null;
else if (b == null)
return a == null;
else
return a.getSpecificType().equals(b.getSpecificType());
}
/**
* Retrieves the common type of each field.
* @return Common type of each field.
*/
public Class getFieldType() {
return fieldType;
}
/**
* Retrieves the type of the object we're modifying.
* @return Type of the object.
*/
public Class getTargetType() {
return targetType;
}
/**
* Retrieves the object we're currently modifying.
* @return Object we're modifying.
*/
public Object getTarget() {
return target;
}
/**
* Retrieve the number of readable types.
* @return Readable types.
*/
public int size() {
return data.size();
}
/**
* Create a new structure modifier for the new field type.
* @param fieldType - common type of each field.
* @param filtered - list of fields after filtering the original modifier.
* @param defaults - list of default values after filtering the original.
* @param converter - the new converter.
* @return A new structure modifier.
*/
protected <T> StructureModifier<T> withFieldType(
Class fieldType, List<Field> filtered,
Map<Field, Integer> defaults, EquivalentConverter<T> converter) {
StructureModifier<T> result = new StructureModifier<T>();
result.initialize(targetType, fieldType, filtered, defaults,
converter, new ConcurrentHashMap<Class, StructureModifier>());
return result;
}
/**
* Retrieves a structure modifier of the same type for a different object target.
* @param target - different target of the same type.
* @return Structure modifier with the new target.
*/
public StructureModifier<TField> withTarget(Object target) {
StructureModifier<TField> copy = new StructureModifier<TField>();
// Create a new instance
copy.initialize(targetType, fieldType, data, defaultFields, converter, subtypeCache);
copy.target = target;
return copy;
}
/**
* Retrieves a structure modifier with the same type and target, but using a new object converter.
* @param converter - the object converter to use.
* @return Structure modifier with the new converter.
*/
@SuppressWarnings("unchecked")
private <T> StructureModifier<T> withConverter(EquivalentConverter<T> converter) {
StructureModifier copy = withTarget(target);
copy.setConverter(converter);
return copy;
}
/**
* Set the current object converter. Should only be called during construction.
* @param converter - current object converter.
*/
protected void setConverter(EquivalentConverter<TField> converter) {
this.converter = converter;
}
/**
* Retrieves a list of the fields matching the constraints of this structure modifier.
* @return List of fields.
*/
public List<Field> getFields() {
return ImmutableList.copyOf(data);
}
/**
* Retrieve every value stored in the fields of the current type.
* @return Every field value.
* @throws FieldAccessException Unable to access one or all of the fields
*/
public List<TField> getValues() throws FieldAccessException {
List<TField> values = new ArrayList<TField>();
for (int i = 0; i < size(); i++) {
values.add(read(i));
}
return values;
}
// Used to generate plausible default values
private static Map<Field, Integer> generateDefaultFields(List<Field> fields) {
Map<Field, Integer> requireDefaults = new HashMap<Field, Integer>();
DefaultInstances generator = DefaultInstances.DEFAULT;
int index = 0;
for (Field field : fields) {
Class<?> type = field.getType();
// First, ignore primitive fields
if (!PrimitiveUtils.isPrimitive(type)) {
// Next, see if we actually can generate a default value
if (generator.getDefault(type) != null) {
// If so, require it
requireDefaults.put(field, index);
}
}
// Increment field index
index++;
}
return requireDefaults;
}
// Used to filter out irrelevant fields
private static List<Field> getFields(Class type, Class superclassExclude) {
List<Field> result = new ArrayList<Field>();
// Retrieve every private and public field
for (Field field : FuzzyReflection.fromClass(type, true).getFields()) {
int mod = field.getModifiers();
// Ignore static, final and "abstract packet" fields
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && (
superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude)
)) {
result.add(field);
}
}
return result;
}
}
@@ -1,181 +1,187 @@
/*
* 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.Field;
/**
* Represents a field that will revert to its original state when this class is garbaged collected.
*
* @author Kristian
*/
public class VolatileField {
private Field field;
private Object container;
// The current and previous values
private Object previous;
private Object current;
// Whether or not we must reset or load
private boolean previousLoaded;
private boolean currentSet;
// Whether or not to break access restrictions
private boolean forceAccess;
/**
* Initializes a volatile field with an associated object.
* @param field - the field.
* @param container - the object this field belongs to.
*/
public VolatileField(Field field, Object container) {
this.field = field;
this.container = container;
}
/**
* Initializes a volatile field with an associated object.
* @param field - the field.
* @param container - the object this field belongs to.
* @param forceAccess - whether or not to override any scope restrictions.
*/
public VolatileField(Field field, Object container, boolean forceAccess) {
this.field = field;
this.container = container;
this.forceAccess = forceAccess;
}
/**
* Retrieves the current field.
* @return The stored field.
*/
public Field getField() {
return field;
}
/**
* Retrieves the object the field is stored.
* @return The reference object.
*/
public Object getContainer() {
return container;
}
/**
* Retrieves whether or not not to override any scope restrictions.
* @return TRUE if we override scope, FALSE otherwise.
*/
public boolean isForceAccess() {
return forceAccess;
}
/**
* Sets whether or not not to override any scope restrictions.
* @param forceAccess - TRUE if we override scope, FALSE otherwise.
*/
public void setForceAccess(boolean forceAccess) {
this.forceAccess = forceAccess;
}
/**
* Retrieves the current field value.
* @return The current field value.
*/
public Object getValue() {
// Retrieve the correct value
if (!currentSet) {
ensureLoaded();
return previous;
} else {
return current;
}
}
/**
* Retrieves the field value before the previous setValue(), unless saveValue() has been called.
* @return Previous value.
*/
public Object getOldValue() {
ensureLoaded();
return previous;
}
/**
* Sets the current value. This will be reverted unless saveValue() is called.
* @param newValue - new field value.
*/
public void setValue(Object newValue) {
// Remember to safe the previous value
ensureLoaded();
try {
FieldUtils.writeField(field, container, newValue, forceAccess);
current = newValue;
currentSet = true;
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read field " + field.getName(), e);
}
}
/**
* Ensure that the current value is still set after this class has been garbaged collected.
*/
public void saveValue() {
previous = current;
currentSet = false;
}
/**
* Revert to the previously set value.
*/
public void revertValue() {
// Reset value.
if (currentSet) {
setValue(previous);
currentSet = false;
}
}
/**
* Determine whether or not we'll need to revert the value.
*/
public boolean isCurrentSet() {
return currentSet;
}
private void ensureLoaded() {
// Load the value if we haven't already
if (!previousLoaded) {
try {
previous = FieldUtils.readField(field, container, forceAccess);
previousLoaded = true;
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read field " + field.getName(), e);
}
}
}
@Override
protected void finalize() throws Throwable {
revertValue();
}
}
/*
* 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.Field;
/**
* Represents a field that will revert to its original state when this class is garbaged collected.
*
* @author Kristian
*/
public class VolatileField {
private Field field;
private Object container;
// The current and previous values
private Object previous;
private Object current;
// Whether or not we must reset or load
private boolean previousLoaded;
private boolean currentSet;
// Whether or not to break access restrictions
private boolean forceAccess;
/**
* Initializes a volatile field with an associated object.
* @param field - the field.
* @param container - the object this field belongs to.
*/
public VolatileField(Field field, Object container) {
this.field = field;
this.container = container;
}
/**
* Initializes a volatile field with an associated object.
* @param field - the field.
* @param container - the object this field belongs to.
* @param forceAccess - whether or not to override any scope restrictions.
*/
public VolatileField(Field field, Object container, boolean forceAccess) {
this.field = field;
this.container = container;
this.forceAccess = forceAccess;
}
/**
* Retrieves the current field.
* @return The stored field.
*/
public Field getField() {
return field;
}
/**
* Retrieves the object the field is stored.
* @return The reference object.
*/
public Object getContainer() {
return container;
}
/**
* Retrieves whether or not not to override any scope restrictions.
* @return TRUE if we override scope, FALSE otherwise.
*/
public boolean isForceAccess() {
return forceAccess;
}
/**
* Sets whether or not not to override any scope restrictions.
* @param forceAccess - TRUE if we override scope, FALSE otherwise.
*/
public void setForceAccess(boolean forceAccess) {
this.forceAccess = forceAccess;
}
/**
* Retrieves the current field value.
* @return The current field value.
*/
public Object getValue() {
// Retrieve the correct value
if (!currentSet) {
ensureLoaded();
return previous;
} else {
return current;
}
}
/**
* Retrieves the field value before the previous setValue(), unless saveValue() has been called.
* @return Previous value.
*/
public Object getOldValue() {
ensureLoaded();
return previous;
}
/**
* Sets the current value. This will be reverted unless saveValue() is called.
* @param newValue - new field value.
*/
public void setValue(Object newValue) {
// Remember to safe the previous value
ensureLoaded();
try {
FieldUtils.writeField(field, container, newValue, forceAccess);
current = newValue;
currentSet = true;
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read field " + field.getName(), e);
}
}
/**
* Ensure that the current value is still set after this class has been garbaged collected.
*/
public void saveValue() {
previous = current;
currentSet = false;
}
/**
* Revert to the previously set value.
*/
public void revertValue() {
// Reset value if it hasn't been changed by anyone else
if (currentSet) {
if (getValue() == current) {
setValue(previous);
currentSet = false;
} else {
// This can be a bad sign
System.out.println(String.format("[ProtocolLib] Unable to switch %s to %s. Expected %s but got %s.",
field.toGenericString(), previous, current, getValue()));
}
}
}
/**
* Determine whether or not we'll need to revert the value.
*/
public boolean isCurrentSet() {
return currentSet;
}
private void ensureLoaded() {
// Load the value if we haven't already
if (!previousLoaded) {
try {
previous = FieldUtils.readField(field, container, forceAccess);
previousLoaded = true;
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read field " + field.getName(), e);
}
}
}
@Override
protected void finalize() throws Throwable {
revertValue();
}
}
@@ -1,183 +1,200 @@
package com.comphenix.protocol.reflect.compiler;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.comphenix.protocol.reflect.StructureModifier;
/**
* Compiles structure modifiers on a background thread.
* <p>
* This is necessary as we cannot block the main thread.
*
* @author Kristian
*/
public class BackgroundCompiler {
// How long to wait for a shutdown
public static final int SHUTDOWN_DELAY_MS = 2000;
// The single background compiler we're using
private static BackgroundCompiler backgroundCompiler;
private StructureCompiler compiler;
private boolean enabled;
private boolean shuttingDown;
private ExecutorService executor;
/**
* Retrieves the current background compiler.
* @return Current background compiler.
*/
public static BackgroundCompiler getInstance() {
return backgroundCompiler;
}
/**
* Sets the single background compiler we're using.
* @param backgroundCompiler - current background compiler, or NULL if the library is not loaded.
*/
public static void setInstance(BackgroundCompiler backgroundCompiler) {
BackgroundCompiler.backgroundCompiler = backgroundCompiler;
}
/**
* Initialize a background compiler.
* @param loader - class loader from Bukkit.
*/
public BackgroundCompiler(ClassLoader loader) {
this(loader, Executors.newSingleThreadExecutor());
}
/**
* Initialize a background compiler utilizing the given thread pool.
* @param loader - class loader from Bukkit.
* @param executor - thread pool we'll use.
*/
public BackgroundCompiler(ClassLoader loader, ExecutorService executor) {
if (loader == null)
throw new IllegalArgumentException("loader cannot be NULL");
if (executor == null)
throw new IllegalArgumentException("executor cannot be NULL");
this.compiler = new StructureCompiler(loader);
this.executor = executor;
this.enabled = true;
}
/**
* Ensure that the indirectly given structure modifier is eventually compiled.
* @param cache - store of structure modifiers.
* @param key - key of the structure modifier to compile.
*/
@SuppressWarnings("rawtypes")
public void scheduleCompilation(final Map<Class, StructureModifier> cache, final Class key) {
@SuppressWarnings("unchecked")
final StructureModifier<Object> uncompiled = cache.get(key);
if (uncompiled != null) {
scheduleCompilation(uncompiled, new CompileListener<Object>() {
@Override
public void onCompiled(StructureModifier<Object> compiledModifier) {
// Update cache
cache.put(key, compiledModifier);
}
});
}
}
/**
* Ensure that the given structure modifier is eventually compiled.
* @param uncompiled - structure modifier to compile.
* @param listener - listener responsible for responding to the compilation.
*/
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
// Only schedule if we're enabled
if (enabled && !shuttingDown) {
// Don't try to schedule anything
if (executor == null || executor.isShutdown())
return;
try {
executor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
StructureModifier<TKey> modifier = uncompiled;
// Do our compilation
modifier = compiler.compile(modifier);
listener.onCompiled(modifier);
// We'll also return the new structure modifier
return modifier;
}
});
} catch (RejectedExecutionException e) {
// Occures when the underlying queue is overflowing. Since the compilation
// is only an optmization and not really essential we'll just log this failure
// and move on.
Logger.getLogger("Minecraft").log(Level.WARNING, "Unable to schedule compilation task.", e);
}
}
}
/**
* Clean up after ourselves using the default timeout.
*/
public void shutdownAll() {
shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS);
}
/**
* Clean up after ourselves.
* @param timeout - the maximum time to wait.
* @param unit - the time unit of the timeout argument.
*/
public void shutdownAll(long timeout, TimeUnit unit) {
setEnabled(false);
shuttingDown = true;
executor.shutdown();
try {
executor.awaitTermination(timeout, unit);
} catch (InterruptedException e) {
// Unlikely to ever occur.
e.printStackTrace();
}
}
/**
* Retrieve whether or not the background compiler is enabled.
* @return TRUE if it is enabled, FALSE otherwise.
*/
public boolean isEnabled() {
return enabled;
}
/**
* Sets whether or not the background compiler is enabled.
* @param enabled - TRUE to enable it, FALSE otherwise.
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Retrieve the current structure compiler.
* @return Current structure compiler.
*/
public StructureCompiler getCompiler() {
return compiler;
}
}
/*
* 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.compiler;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.comphenix.protocol.reflect.StructureModifier;
/**
* Compiles structure modifiers on a background thread.
* <p>
* This is necessary as we cannot block the main thread.
*
* @author Kristian
*/
public class BackgroundCompiler {
// How long to wait for a shutdown
public static final int SHUTDOWN_DELAY_MS = 2000;
// The single background compiler we're using
private static BackgroundCompiler backgroundCompiler;
private StructureCompiler compiler;
private boolean enabled;
private boolean shuttingDown;
private ExecutorService executor;
/**
* Retrieves the current background compiler.
* @return Current background compiler.
*/
public static BackgroundCompiler getInstance() {
return backgroundCompiler;
}
/**
* Sets the single background compiler we're using.
* @param backgroundCompiler - current background compiler, or NULL if the library is not loaded.
*/
public static void setInstance(BackgroundCompiler backgroundCompiler) {
BackgroundCompiler.backgroundCompiler = backgroundCompiler;
}
/**
* Initialize a background compiler.
* @param loader - class loader from Bukkit.
*/
public BackgroundCompiler(ClassLoader loader) {
this(loader, Executors.newSingleThreadExecutor());
}
/**
* Initialize a background compiler utilizing the given thread pool.
* @param loader - class loader from Bukkit.
* @param executor - thread pool we'll use.
*/
public BackgroundCompiler(ClassLoader loader, ExecutorService executor) {
if (loader == null)
throw new IllegalArgumentException("loader cannot be NULL");
if (executor == null)
throw new IllegalArgumentException("executor cannot be NULL");
this.compiler = new StructureCompiler(loader);
this.executor = executor;
this.enabled = true;
}
/**
* Ensure that the indirectly given structure modifier is eventually compiled.
* @param cache - store of structure modifiers.
* @param key - key of the structure modifier to compile.
*/
@SuppressWarnings("rawtypes")
public void scheduleCompilation(final Map<Class, StructureModifier> cache, final Class key) {
@SuppressWarnings("unchecked")
final StructureModifier<Object> uncompiled = cache.get(key);
if (uncompiled != null) {
scheduleCompilation(uncompiled, new CompileListener<Object>() {
@Override
public void onCompiled(StructureModifier<Object> compiledModifier) {
// Update cache
cache.put(key, compiledModifier);
}
});
}
}
/**
* Ensure that the given structure modifier is eventually compiled.
* @param uncompiled - structure modifier to compile.
* @param listener - listener responsible for responding to the compilation.
*/
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
// Only schedule if we're enabled
if (enabled && !shuttingDown) {
// Don't try to schedule anything
if (executor == null || executor.isShutdown())
return;
try {
executor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
StructureModifier<TKey> modifier = uncompiled;
// Do our compilation
modifier = compiler.compile(modifier);
listener.onCompiled(modifier);
// We'll also return the new structure modifier
return modifier;
}
});
} catch (RejectedExecutionException e) {
// Occures when the underlying queue is overflowing. Since the compilation
// is only an optmization and not really essential we'll just log this failure
// and move on.
Logger.getLogger("Minecraft").log(Level.WARNING, "Unable to schedule compilation task.", e);
}
}
}
/**
* Clean up after ourselves using the default timeout.
*/
public void shutdownAll() {
shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS);
}
/**
* Clean up after ourselves.
* @param timeout - the maximum time to wait.
* @param unit - the time unit of the timeout argument.
*/
public void shutdownAll(long timeout, TimeUnit unit) {
setEnabled(false);
shuttingDown = true;
executor.shutdown();
try {
executor.awaitTermination(timeout, unit);
} catch (InterruptedException e) {
// Unlikely to ever occur.
e.printStackTrace();
}
}
/**
* Retrieve whether or not the background compiler is enabled.
* @return TRUE if it is enabled, FALSE otherwise.
*/
public boolean isEnabled() {
return enabled;
}
/**
* Sets whether or not the background compiler is enabled.
* @param enabled - TRUE to enable it, FALSE otherwise.
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Retrieve the current structure compiler.
* @return Current structure compiler.
*/
public StructureCompiler getCompiler() {
return compiler;
}
}
@@ -1,275 +1,292 @@
package com.comphenix.protocol.reflect.compiler;
import net.sf.cglib.asm.*;
/**
* Used by the compiler to automatically box and unbox values.
*/
class BoxingHelper {
private final static Type BYTE_TYPE = Type.getObjectType("java/lang/Byte");
private final static Type BOOLEAN_TYPE = Type.getObjectType("java/lang/Boolean");
private final static Type SHORT_TYPE = Type.getObjectType("java/lang/Short");
private final static Type CHARACTER_TYPE = Type.getObjectType("java/lang/Character");
private final static Type INTEGER_TYPE = Type.getObjectType("java/lang/Integer");
private final static Type FLOAT_TYPE = Type.getObjectType("java/lang/Float");
private final static Type LONG_TYPE = Type.getObjectType("java/lang/Long");
private final static Type DOUBLE_TYPE = Type.getObjectType("java/lang/Double");
private final static Type NUMBER_TYPE = Type.getObjectType("java/lang/Number");
private final static Type OBJECT_TYPE = Type.getObjectType("java/lang/Object");
private final static MethodDescriptor BOOLEAN_VALUE = MethodDescriptor.getMethod("boolean booleanValue()");
private final static MethodDescriptor CHAR_VALUE = MethodDescriptor.getMethod("char charValue()");
private final static MethodDescriptor INT_VALUE = MethodDescriptor.getMethod("int intValue()");
private final static MethodDescriptor FLOAT_VALUE = MethodDescriptor.getMethod("float floatValue()");
private final static MethodDescriptor LONG_VALUE = MethodDescriptor.getMethod("long longValue()");
private final static MethodDescriptor DOUBLE_VALUE = MethodDescriptor.getMethod("double doubleValue()");
private MethodVisitor mv;
public BoxingHelper(MethodVisitor mv) {
this.mv = mv;
}
/**
* Generates the instructions to box the top stack value. This value is
* replaced by its boxed equivalent on top of the stack.
*
* @param type the type of the top stack value.
*/
public void box(final Type type){
if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
return;
}
if(type == Type.VOID_TYPE) {
push((String) null);
} else {
Type boxed = type;
switch(type.getSort()) {
case Type.BYTE:
boxed = BYTE_TYPE;
break;
case Type.BOOLEAN:
boxed = BOOLEAN_TYPE;
break;
case Type.SHORT:
boxed = SHORT_TYPE;
break;
case Type.CHAR:
boxed = CHARACTER_TYPE;
break;
case Type.INT:
boxed = INTEGER_TYPE;
break;
case Type.FLOAT:
boxed = FLOAT_TYPE;
break;
case Type.LONG:
boxed = LONG_TYPE;
break;
case Type.DOUBLE:
boxed = DOUBLE_TYPE;
break;
}
newInstance(boxed);
if(type.getSize() == 2) {
// Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o
dupX2();
dupX2();
pop();
} else {
// p -> po -> opo -> oop -> o
dupX1();
swap();
}
invokeConstructor(boxed, new MethodDescriptor("<init>", Type.VOID_TYPE, new Type[] {type}));
}
}
/**
* Generates the instruction to invoke a constructor.
*
* @param type the class in which the constructor is defined.
* @param method the constructor to be invoked.
*/
public void invokeConstructor(final Type type, final MethodDescriptor method){
invokeInsn(Opcodes.INVOKESPECIAL, type, method);
}
/**
* Generates a DUP_X1 instruction.
*/
public void dupX1(){
mv.visitInsn(Opcodes.DUP_X1);
}
/**
* Generates a DUP_X2 instruction.
*/
public void dupX2(){
mv.visitInsn(Opcodes.DUP_X2);
}
/**
* Generates a POP instruction.
*/
public void pop(){
mv.visitInsn(Opcodes.POP);
}
/**
* Generates a SWAP instruction.
*/
public void swap(){
mv.visitInsn(Opcodes.SWAP);
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack.
*/
public void push(final boolean value){
push(value ? 1 : 0);
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack.
*/
public void push(final int value) {
if (value >= -1 && value <= 5) {
mv.visitInsn(Opcodes.ICONST_0 + value);
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
mv.visitIntInsn(Opcodes.BIPUSH, value);
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
mv.visitIntInsn(Opcodes.SIPUSH, value);
} else {
mv.visitLdcInsn(new Integer(value));
}
}
/**
* Generates the instruction to create a new object.
*
* @param type the class of the object to be created.
*/
public void newInstance(final Type type){
typeInsn(Opcodes.NEW, type);
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack. May be <tt>null</tt>.
*/
public void push(final String value) {
if (value == null) {
mv.visitInsn(Opcodes.ACONST_NULL);
} else {
mv.visitLdcInsn(value);
}
}
/**
* Generates the instructions to unbox the top stack value. This value is
* replaced by its unboxed equivalent on top of the stack.
*
* @param type
* the type of the top stack value.
*/
public void unbox(final Type type){
Type t = NUMBER_TYPE;
MethodDescriptor sig = null;
switch(type.getSort()) {
case Type.VOID:
return;
case Type.CHAR:
t = CHARACTER_TYPE;
sig = CHAR_VALUE;
break;
case Type.BOOLEAN:
t = BOOLEAN_TYPE;
sig = BOOLEAN_VALUE;
break;
case Type.DOUBLE:
sig = DOUBLE_VALUE;
break;
case Type.FLOAT:
sig = FLOAT_VALUE;
break;
case Type.LONG:
sig = LONG_VALUE;
break;
case Type.INT:
case Type.SHORT:
case Type.BYTE:
sig = INT_VALUE;
}
if(sig == null) {
checkCast(type);
} else {
checkCast(t);
invokeVirtual(t, sig);
}
}
/**
* Generates the instruction to check that the top stack value is of the
* given type.
*
* @param type a class or interface type.
*/
public void checkCast(final Type type){
if(!type.equals(OBJECT_TYPE)) {
typeInsn(Opcodes.CHECKCAST, type);
}
}
/**
* Generates the instruction to invoke a normal method.
*
* @param owner the class in which the method is defined.
* @param method the method to be invoked.
*/
public void invokeVirtual(final Type owner, final MethodDescriptor method){
invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method);
}
/**
* Generates an invoke method instruction.
*
* @param opcode the instruction's opcode.
* @param type the class in which the method is defined.
* @param method the method to be invoked.
*/
private void invokeInsn(final int opcode, final Type type, final MethodDescriptor method){
String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName();
mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor());
}
/**
* Generates a type dependent instruction.
*
* @param opcode the instruction's opcode.
* @param type the instruction's operand.
*/
private void typeInsn(final int opcode, final Type type){
String desc;
if(type.getSort() == Type.ARRAY) {
desc = type.getDescriptor();
} else {
desc = type.getInternalName();
}
mv.visitTypeInsn(opcode, desc);
}
}
/*
* 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.compiler;
import net.sf.cglib.asm.*;
/**
* Used by the compiler to automatically box and unbox values.
*/
class BoxingHelper {
private final static Type BYTE_TYPE = Type.getObjectType("java/lang/Byte");
private final static Type BOOLEAN_TYPE = Type.getObjectType("java/lang/Boolean");
private final static Type SHORT_TYPE = Type.getObjectType("java/lang/Short");
private final static Type CHARACTER_TYPE = Type.getObjectType("java/lang/Character");
private final static Type INTEGER_TYPE = Type.getObjectType("java/lang/Integer");
private final static Type FLOAT_TYPE = Type.getObjectType("java/lang/Float");
private final static Type LONG_TYPE = Type.getObjectType("java/lang/Long");
private final static Type DOUBLE_TYPE = Type.getObjectType("java/lang/Double");
private final static Type NUMBER_TYPE = Type.getObjectType("java/lang/Number");
private final static Type OBJECT_TYPE = Type.getObjectType("java/lang/Object");
private final static MethodDescriptor BOOLEAN_VALUE = MethodDescriptor.getMethod("boolean booleanValue()");
private final static MethodDescriptor CHAR_VALUE = MethodDescriptor.getMethod("char charValue()");
private final static MethodDescriptor INT_VALUE = MethodDescriptor.getMethod("int intValue()");
private final static MethodDescriptor FLOAT_VALUE = MethodDescriptor.getMethod("float floatValue()");
private final static MethodDescriptor LONG_VALUE = MethodDescriptor.getMethod("long longValue()");
private final static MethodDescriptor DOUBLE_VALUE = MethodDescriptor.getMethod("double doubleValue()");
private MethodVisitor mv;
public BoxingHelper(MethodVisitor mv) {
this.mv = mv;
}
/**
* Generates the instructions to box the top stack value. This value is
* replaced by its boxed equivalent on top of the stack.
*
* @param type the type of the top stack value.
*/
public void box(final Type type){
if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
return;
}
if(type == Type.VOID_TYPE) {
push((String) null);
} else {
Type boxed = type;
switch(type.getSort()) {
case Type.BYTE:
boxed = BYTE_TYPE;
break;
case Type.BOOLEAN:
boxed = BOOLEAN_TYPE;
break;
case Type.SHORT:
boxed = SHORT_TYPE;
break;
case Type.CHAR:
boxed = CHARACTER_TYPE;
break;
case Type.INT:
boxed = INTEGER_TYPE;
break;
case Type.FLOAT:
boxed = FLOAT_TYPE;
break;
case Type.LONG:
boxed = LONG_TYPE;
break;
case Type.DOUBLE:
boxed = DOUBLE_TYPE;
break;
}
newInstance(boxed);
if(type.getSize() == 2) {
// Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o
dupX2();
dupX2();
pop();
} else {
// p -> po -> opo -> oop -> o
dupX1();
swap();
}
invokeConstructor(boxed, new MethodDescriptor("<init>", Type.VOID_TYPE, new Type[] {type}));
}
}
/**
* Generates the instruction to invoke a constructor.
*
* @param type the class in which the constructor is defined.
* @param method the constructor to be invoked.
*/
public void invokeConstructor(final Type type, final MethodDescriptor method){
invokeInsn(Opcodes.INVOKESPECIAL, type, method);
}
/**
* Generates a DUP_X1 instruction.
*/
public void dupX1(){
mv.visitInsn(Opcodes.DUP_X1);
}
/**
* Generates a DUP_X2 instruction.
*/
public void dupX2(){
mv.visitInsn(Opcodes.DUP_X2);
}
/**
* Generates a POP instruction.
*/
public void pop(){
mv.visitInsn(Opcodes.POP);
}
/**
* Generates a SWAP instruction.
*/
public void swap(){
mv.visitInsn(Opcodes.SWAP);
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack.
*/
public void push(final boolean value){
push(value ? 1 : 0);
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack.
*/
public void push(final int value) {
if (value >= -1 && value <= 5) {
mv.visitInsn(Opcodes.ICONST_0 + value);
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
mv.visitIntInsn(Opcodes.BIPUSH, value);
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
mv.visitIntInsn(Opcodes.SIPUSH, value);
} else {
mv.visitLdcInsn(new Integer(value));
}
}
/**
* Generates the instruction to create a new object.
*
* @param type the class of the object to be created.
*/
public void newInstance(final Type type){
typeInsn(Opcodes.NEW, type);
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack. May be <tt>null</tt>.
*/
public void push(final String value) {
if (value == null) {
mv.visitInsn(Opcodes.ACONST_NULL);
} else {
mv.visitLdcInsn(value);
}
}
/**
* Generates the instructions to unbox the top stack value. This value is
* replaced by its unboxed equivalent on top of the stack.
*
* @param type
* the type of the top stack value.
*/
public void unbox(final Type type){
Type t = NUMBER_TYPE;
MethodDescriptor sig = null;
switch(type.getSort()) {
case Type.VOID:
return;
case Type.CHAR:
t = CHARACTER_TYPE;
sig = CHAR_VALUE;
break;
case Type.BOOLEAN:
t = BOOLEAN_TYPE;
sig = BOOLEAN_VALUE;
break;
case Type.DOUBLE:
sig = DOUBLE_VALUE;
break;
case Type.FLOAT:
sig = FLOAT_VALUE;
break;
case Type.LONG:
sig = LONG_VALUE;
break;
case Type.INT:
case Type.SHORT:
case Type.BYTE:
sig = INT_VALUE;
}
if(sig == null) {
checkCast(type);
} else {
checkCast(t);
invokeVirtual(t, sig);
}
}
/**
* Generates the instruction to check that the top stack value is of the
* given type.
*
* @param type a class or interface type.
*/
public void checkCast(final Type type){
if(!type.equals(OBJECT_TYPE)) {
typeInsn(Opcodes.CHECKCAST, type);
}
}
/**
* Generates the instruction to invoke a normal method.
*
* @param owner the class in which the method is defined.
* @param method the method to be invoked.
*/
public void invokeVirtual(final Type owner, final MethodDescriptor method){
invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method);
}
/**
* Generates an invoke method instruction.
*
* @param opcode the instruction's opcode.
* @param type the class in which the method is defined.
* @param method the method to be invoked.
*/
private void invokeInsn(final int opcode, final Type type, final MethodDescriptor method){
String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName();
mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor());
}
/**
* Generates a type dependent instruction.
*
* @param opcode the instruction's opcode.
* @param type the instruction's operand.
*/
private void typeInsn(final int opcode, final Type type){
String desc;
if(type.getSort() == Type.ARRAY) {
desc = type.getDescriptor();
} else {
desc = type.getInternalName();
}
mv.visitTypeInsn(opcode, desc);
}
}
@@ -0,0 +1,34 @@
/*
* 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.compiler;
import com.comphenix.protocol.reflect.StructureModifier;
/**
* Used to save the result of an compilation.
*
* @author Kristian
* @param <TKey> - type of the structure modifier field.
*/
public interface CompileListener<TKey> {
/**
* Invoked when a structure modifier has been successfully compiled.
* @param compiledModifier - the compiled structure modifier.
*/
public void onCompiled(StructureModifier<TKey> compiledModifier);
}
@@ -1,73 +1,90 @@
package com.comphenix.protocol.reflect.compiler;
import java.lang.reflect.Field;
import java.util.Map;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
/**
* Represents a compiled structure modifier.
*
* @author Kristian
* @param <TField> Field type.
*/
public abstract class CompiledStructureModifier<TField> extends StructureModifier<TField> {
// Used to compile instances of structure modifiers
protected StructureCompiler compiler;
public CompiledStructureModifier() {
super();
customConvertHandling = true;
}
// Speed up the default writer
@SuppressWarnings("unchecked")
@Override
public StructureModifier<TField> writeDefaults() throws FieldAccessException {
DefaultInstances generator = DefaultInstances.DEFAULT;
// Write a default instance to every field
for (Map.Entry<Field, Integer> entry : defaultFields.entrySet()) {
Integer index = entry.getValue();
Field field = entry.getKey();
write(index, (TField) generator.getDefault(field.getType()));
}
return this;
}
@SuppressWarnings("unchecked")
@Override
public final TField read(int fieldIndex) throws FieldAccessException {
Object result = readGenerated(fieldIndex);
if (converter != null)
return converter.getSpecific(result);
else
return (TField) result;
}
protected abstract Object readGenerated(int fieldIndex) throws FieldAccessException;
@SuppressWarnings("unchecked")
@Override
public StructureModifier<TField> write(int index, Object value) throws FieldAccessException {
if (converter != null)
value = converter.getGeneric((TField) value);
return writeGenerated(index, value);
}
protected abstract StructureModifier<TField> writeGenerated(int index, Object value) throws FieldAccessException;
@Override
public StructureModifier<TField> withTarget(Object target) {
if (compiler != null)
return compiler.compile(super.withTarget(target));
else
return super.withTarget(target);
}
}
/*
* 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.compiler;
import java.lang.reflect.Field;
import java.util.Map;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
/**
* Represents a compiled structure modifier.
*
* @author Kristian
* @param <TField> Field type.
*/
public abstract class CompiledStructureModifier<TField> extends StructureModifier<TField> {
// Used to compile instances of structure modifiers
protected StructureCompiler compiler;
public CompiledStructureModifier() {
super();
customConvertHandling = true;
}
// Speed up the default writer
@SuppressWarnings("unchecked")
@Override
public StructureModifier<TField> writeDefaults() throws FieldAccessException {
DefaultInstances generator = DefaultInstances.DEFAULT;
// Write a default instance to every field
for (Map.Entry<Field, Integer> entry : defaultFields.entrySet()) {
Integer index = entry.getValue();
Field field = entry.getKey();
write(index, (TField) generator.getDefault(field.getType()));
}
return this;
}
@SuppressWarnings("unchecked")
@Override
public final TField read(int fieldIndex) throws FieldAccessException {
Object result = readGenerated(fieldIndex);
if (converter != null)
return converter.getSpecific(result);
else
return (TField) result;
}
protected abstract Object readGenerated(int fieldIndex) throws FieldAccessException;
@SuppressWarnings("unchecked")
@Override
public StructureModifier<TField> write(int index, Object value) throws FieldAccessException {
if (converter != null)
value = converter.getGeneric((TField) value);
return writeGenerated(index, value);
}
protected abstract StructureModifier<TField> writeGenerated(int index, Object value) throws FieldAccessException;
@Override
public StructureModifier<TField> withTarget(Object target) {
if (compiler != null)
return compiler.compile(super.withTarget(target));
else
return super.withTarget(target);
}
}
@@ -1,220 +1,237 @@
package com.comphenix.protocol.reflect.compiler;
import java.util.HashMap;
import java.util.Map;
import net.sf.cglib.asm.Type;
/**
* Represents a method.
*/
class MethodDescriptor {
/**
* The method name.
*/
private final String name;
/**
* The method descriptor.
*/
private final String desc;
/**
* Maps primitive Java type names to their descriptors.
*/
private static final Map<String, String> DESCRIPTORS;
static {
DESCRIPTORS = new HashMap<String, String>();
DESCRIPTORS.put("void", "V");
DESCRIPTORS.put("byte", "B");
DESCRIPTORS.put("char", "C");
DESCRIPTORS.put("double", "D");
DESCRIPTORS.put("float", "F");
DESCRIPTORS.put("int", "I");
DESCRIPTORS.put("long", "J");
DESCRIPTORS.put("short", "S");
DESCRIPTORS.put("boolean", "Z");
}
/**
* Creates a new {@link Method}.
*
* @param name the method's name.
* @param desc the method's descriptor.
*/
public MethodDescriptor(final String name, final String desc) {
this.name = name;
this.desc = desc;
}
/**
* Creates a new {@link Method}.
*
* @param name the method's name.
* @param returnType the method's return type.
* @param argumentTypes the method's argument types.
*/
public MethodDescriptor(
final String name,
final Type returnType,
final Type[] argumentTypes)
{
this(name, Type.getMethodDescriptor(returnType, argumentTypes));
}
/**
* Returns a {@link Method} corresponding to the given Java method
* declaration.
*
* @param method a Java method declaration, without argument names, of the
* form "returnType name (argumentType1, ... argumentTypeN)", where
* the types are in plain Java (e.g. "int", "float",
* "java.util.List", ...). Classes of the java.lang package can be
* specified by their unqualified name; all other classes names must
* be fully qualified.
* @return a {@link Method} corresponding to the given Java method
* declaration.
* @throws IllegalArgumentException if <code>method</code> could not get
* parsed.
*/
public static MethodDescriptor getMethod(final String method)
throws IllegalArgumentException
{
return getMethod(method, false);
}
/**
* Returns a {@link Method} corresponding to the given Java method
* declaration.
*
* @param method a Java method declaration, without argument names, of the
* form "returnType name (argumentType1, ... argumentTypeN)", where
* the types are in plain Java (e.g. "int", "float",
* "java.util.List", ...). Classes of the java.lang package may be
* specified by their unqualified name, depending on the
* defaultPackage argument; all other classes names must be fully
* qualified.
* @param defaultPackage true if unqualified class names belong to the
* default package, or false if they correspond to java.lang classes.
* For instance "Object" means "Object" if this option is true, or
* "java.lang.Object" otherwise.
* @return a {@link Method} corresponding to the given Java method
* declaration.
* @throws IllegalArgumentException if <code>method</code> could not get
* parsed.
*/
public static MethodDescriptor getMethod(
final String method,
final boolean defaultPackage) throws IllegalArgumentException
{
int space = method.indexOf(' ');
int start = method.indexOf('(', space) + 1;
int end = method.indexOf(')', start);
if (space == -1 || start == -1 || end == -1) {
throw new IllegalArgumentException();
}
String returnType = method.substring(0, space);
String methodName = method.substring(space + 1, start - 1).trim();
StringBuffer sb = new StringBuffer();
sb.append('(');
int p;
do {
String s;
p = method.indexOf(',', start);
if (p == -1) {
s = map(method.substring(start, end).trim(), defaultPackage);
} else {
s = map(method.substring(start, p).trim(), defaultPackage);
start = p + 1;
}
sb.append(s);
} while (p != -1);
sb.append(')');
sb.append(map(returnType, defaultPackage));
return new MethodDescriptor(methodName, sb.toString());
}
private static String map(final String type, final boolean defaultPackage) {
if ("".equals(type)) {
return type;
}
StringBuffer sb = new StringBuffer();
int index = 0;
while ((index = type.indexOf("[]", index) + 1) > 0) {
sb.append('[');
}
String t = type.substring(0, type.length() - sb.length() * 2);
String desc = (String) DESCRIPTORS.get(t);
if (desc != null) {
sb.append(desc);
} else {
sb.append('L');
if (t.indexOf('.') < 0) {
if (!defaultPackage) {
sb.append("java/lang/");
}
sb.append(t);
} else {
sb.append(t.replace('.', '/'));
}
sb.append(';');
}
return sb.toString();
}
/**
* Returns the name of the method described by this object.
*
* @return the name of the method described by this object.
*/
public String getName() {
return name;
}
/**
* Returns the descriptor of the method described by this object.
*
* @return the descriptor of the method described by this object.
*/
public String getDescriptor() {
return desc;
}
/**
* Returns the return type of the method described by this object.
*
* @return the return type of the method described by this object.
*/
public Type getReturnType() {
return Type.getReturnType(desc);
}
/**
* Returns the argument types of the method described by this object.
*
* @return the argument types of the method described by this object.
*/
public Type[] getArgumentTypes() {
return Type.getArgumentTypes(desc);
}
public String toString() {
return name + desc;
}
public boolean equals(final Object o) {
if (!(o instanceof MethodDescriptor)) {
return false;
}
MethodDescriptor other = (MethodDescriptor) o;
return name.equals(other.name) && desc.equals(other.desc);
}
public int hashCode() {
return name.hashCode() ^ desc.hashCode();
}
}
/*
* 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.compiler;
import java.util.HashMap;
import java.util.Map;
import net.sf.cglib.asm.Type;
/**
* Represents a method.
*/
class MethodDescriptor {
/**
* The method name.
*/
private final String name;
/**
* The method descriptor.
*/
private final String desc;
/**
* Maps primitive Java type names to their descriptors.
*/
private static final Map<String, String> DESCRIPTORS;
static {
DESCRIPTORS = new HashMap<String, String>();
DESCRIPTORS.put("void", "V");
DESCRIPTORS.put("byte", "B");
DESCRIPTORS.put("char", "C");
DESCRIPTORS.put("double", "D");
DESCRIPTORS.put("float", "F");
DESCRIPTORS.put("int", "I");
DESCRIPTORS.put("long", "J");
DESCRIPTORS.put("short", "S");
DESCRIPTORS.put("boolean", "Z");
}
/**
* Creates a new {@link Method}.
*
* @param name the method's name.
* @param desc the method's descriptor.
*/
public MethodDescriptor(final String name, final String desc) {
this.name = name;
this.desc = desc;
}
/**
* Creates a new {@link Method}.
*
* @param name the method's name.
* @param returnType the method's return type.
* @param argumentTypes the method's argument types.
*/
public MethodDescriptor(
final String name,
final Type returnType,
final Type[] argumentTypes)
{
this(name, Type.getMethodDescriptor(returnType, argumentTypes));
}
/**
* Returns a {@link Method} corresponding to the given Java method
* declaration.
*
* @param method a Java method declaration, without argument names, of the
* form "returnType name (argumentType1, ... argumentTypeN)", where
* the types are in plain Java (e.g. "int", "float",
* "java.util.List", ...). Classes of the java.lang package can be
* specified by their unqualified name; all other classes names must
* be fully qualified.
* @return a {@link Method} corresponding to the given Java method
* declaration.
* @throws IllegalArgumentException if <code>method</code> could not get
* parsed.
*/
public static MethodDescriptor getMethod(final String method)
throws IllegalArgumentException
{
return getMethod(method, false);
}
/**
* Returns a {@link Method} corresponding to the given Java method
* declaration.
*
* @param method a Java method declaration, without argument names, of the
* form "returnType name (argumentType1, ... argumentTypeN)", where
* the types are in plain Java (e.g. "int", "float",
* "java.util.List", ...). Classes of the java.lang package may be
* specified by their unqualified name, depending on the
* defaultPackage argument; all other classes names must be fully
* qualified.
* @param defaultPackage true if unqualified class names belong to the
* default package, or false if they correspond to java.lang classes.
* For instance "Object" means "Object" if this option is true, or
* "java.lang.Object" otherwise.
* @return a {@link Method} corresponding to the given Java method
* declaration.
* @throws IllegalArgumentException if <code>method</code> could not get
* parsed.
*/
public static MethodDescriptor getMethod(
final String method,
final boolean defaultPackage) throws IllegalArgumentException
{
int space = method.indexOf(' ');
int start = method.indexOf('(', space) + 1;
int end = method.indexOf(')', start);
if (space == -1 || start == -1 || end == -1) {
throw new IllegalArgumentException();
}
String returnType = method.substring(0, space);
String methodName = method.substring(space + 1, start - 1).trim();
StringBuffer sb = new StringBuffer();
sb.append('(');
int p;
do {
String s;
p = method.indexOf(',', start);
if (p == -1) {
s = map(method.substring(start, end).trim(), defaultPackage);
} else {
s = map(method.substring(start, p).trim(), defaultPackage);
start = p + 1;
}
sb.append(s);
} while (p != -1);
sb.append(')');
sb.append(map(returnType, defaultPackage));
return new MethodDescriptor(methodName, sb.toString());
}
private static String map(final String type, final boolean defaultPackage) {
if ("".equals(type)) {
return type;
}
StringBuffer sb = new StringBuffer();
int index = 0;
while ((index = type.indexOf("[]", index) + 1) > 0) {
sb.append('[');
}
String t = type.substring(0, type.length() - sb.length() * 2);
String desc = (String) DESCRIPTORS.get(t);
if (desc != null) {
sb.append(desc);
} else {
sb.append('L');
if (t.indexOf('.') < 0) {
if (!defaultPackage) {
sb.append("java/lang/");
}
sb.append(t);
} else {
sb.append(t.replace('.', '/'));
}
sb.append(';');
}
return sb.toString();
}
/**
* Returns the name of the method described by this object.
*
* @return the name of the method described by this object.
*/
public String getName() {
return name;
}
/**
* Returns the descriptor of the method described by this object.
*
* @return the descriptor of the method described by this object.
*/
public String getDescriptor() {
return desc;
}
/**
* Returns the return type of the method described by this object.
*
* @return the return type of the method described by this object.
*/
public Type getReturnType() {
return Type.getReturnType(desc);
}
/**
* Returns the argument types of the method described by this object.
*
* @return the argument types of the method described by this object.
*/
public Type[] getArgumentTypes() {
return Type.getArgumentTypes(desc);
}
public String toString() {
return name + desc;
}
public boolean equals(final Object o) {
if (!(o instanceof MethodDescriptor)) {
return false;
}
MethodDescriptor other = (MethodDescriptor) o;
return name.equals(other.name) && desc.equals(other.desc);
}
public int hashCode() {
return name.hashCode() ^ desc.hashCode();
}
}
@@ -1,443 +1,460 @@
package com.comphenix.protocol.reflect.compiler;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.comphenix.protocol.reflect.PrimitiveUtils;
import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.base.Objects;
import net.sf.cglib.asm.*;
// public class CompiledStructureModifierPacket20<TField> extends CompiledStructureModifier<TField> {
//
// private Packet20NamedEntitySpawn typedTarget;
//
// public CompiledStructureModifierPacket20(StructureModifier<TField> other, StructureCompiler compiler) {
// super();
// initialize(other);
// this.target = other.getTarget();
// this.typedTarget = (Packet20NamedEntitySpawn) target;
// this.compiler = compiler;
// }
//
// @Override
// protected Object readGenerated(int fieldIndex) throws FieldAccessException {
//
// Packet20NamedEntitySpawn target = typedTarget;
//
// switch (fieldIndex) {
// case 0: return (Object) target.a;
// case 1: return (Object) target.b;
// case 2: return (Object) target.c;
// case 3: return super.read(fieldIndex);
// case 4: return super.read(fieldIndex);
// case 5: return (Object) target.f;
// case 6: return (Object) target.g;
// case 7: return (Object) target.h;
// default:
// throw new FieldAccessException("Invalid index " + fieldIndex);
// }
// }
//
// @Override
// protected StructureModifier<TField> writeGenerated(int index, Object value) throws FieldAccessException {
//
// Packet20NamedEntitySpawn target = typedTarget;
//
// switch (index) {
// case 0: target.a = (Integer) value; break;
// case 1: target.b = (String) value; break;
// case 2: target.c = (Integer) value; break;
// case 3: target.d = (Integer) value; break;
// case 4: super.write(index, value); break;
// case 5: super.write(index, value); break;
// case 6: target.g = (Byte) value; break;
// case 7: target.h = (Integer) value; break;
// default:
// throw new FieldAccessException("Invalid index " + index);
// }
//
// // Chaining
// return this;
// }
// }
/**
* Represents a StructureModifier compiler.
*
* @author Kristian
*/
public final class StructureCompiler {
// Used to store generated classes of different types
@SuppressWarnings("rawtypes")
private class StructureKey {
private Class targetType;
private Class fieldType;
public StructureKey(Class targetType, Class fieldType) {
this.targetType = targetType;
this.fieldType = fieldType;
}
@Override
public int hashCode() {
return Objects.hashCode(targetType, fieldType);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof StructureKey) {
StructureKey other = (StructureKey) obj;
return Objects.equal(targetType, other.targetType) &&
Objects.equal(fieldType, other.fieldType);
}
return false;
}
}
// Used to load classes
private volatile static Method defineMethod;
@SuppressWarnings("rawtypes")
private Map<StructureKey, Class> compiledCache = new HashMap<StructureKey, Class>();
// The class loader we'll store our classes
private ClassLoader loader;
// References to other classes
private static String PACKAGE_NAME = "com/comphenix/protocol/reflect/compiler";
private static String SUPER_CLASS = "com/comphenix/protocol/reflect/StructureModifier";
private static String COMPILED_CLASS = PACKAGE_NAME + "/CompiledStructureModifier";
private static String FIELD_EXCEPTION_CLASS = "com/comphenix/protocol/reflect/FieldAccessException";
/**
* Construct a structure compiler.
* @param loader - main class loader.
*/
StructureCompiler(ClassLoader loader) {
this.loader = loader;
}
/**
* Compiles the given structure modifier.
* <p>
* WARNING: Do NOT call this method in the main thread. Compiling may easily take 10 ms, which is already
* over 1/4 of a tick (50 ms). Let the background thread automatically compile the structure modifiers instead.
* @param source - structure modifier to compile.
* @return A compiled structure modifier.
*/
@SuppressWarnings("unchecked")
public synchronized <TField> StructureModifier<TField> compile(StructureModifier<TField> source) {
// We cannot optimize a structure modifier with no public fields
if (!isAnyPublic(source.getFields())) {
return source;
}
StructureKey key = new StructureKey(source.getTargetType(), source.getFieldType());
Class<?> compiledClass = compiledCache.get(key);
if (!compiledCache.containsKey(key)) {
compiledClass = generateClass(source);
compiledCache.put(key, compiledClass);
}
// Next, create an instance of this class
try {
return (StructureModifier<TField>) compiledClass.getConstructor(
StructureModifier.class, StructureCompiler.class).
newInstance(source, this);
} catch (IllegalArgumentException e) {
throw new IllegalStateException("Used invalid parameters in instance creation", e);
} catch (SecurityException e) {
throw new RuntimeException("Security limitation!", e);
} catch (InstantiationException e) {
throw new RuntimeException("Error occured while instancing generated class.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Security limitation! Cannot create instance of dynamic class.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error occured while instancing generated class.", e);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Cannot happen.", e);
}
}
private <TField> Class<?> generateClass(StructureModifier<TField> source) {
ClassWriter cw = new ClassWriter(0);
@SuppressWarnings("rawtypes")
Class targetType = source.getTargetType();
String className = "CompiledStructure$" + targetType.getSimpleName() + source.getFieldType().getSimpleName();
String targetSignature = Type.getDescriptor(targetType);
String targetName = targetType.getName().replace('.', '/');
try {
// This class might have been generated before. Try to load it.
Class<?> before = loader.loadClass(PACKAGE_NAME.replace('/', '.') + "." + className);
if (before != null)
return before;
} catch (ClassNotFoundException e) {
// That's ok.
}
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className,
"<TField:Ljava/lang/Object;>L" + COMPILED_CLASS + "<TTField;>;",
COMPILED_CLASS, null);
createFields(cw, targetSignature);
createConstructor(cw, className, targetSignature, targetName);
createReadMethod(cw, className, source.getFields(), targetSignature, targetName);
createWriteMethod(cw, className, source.getFields(), targetSignature, targetName);
cw.visitEnd();
byte[] data = cw.toByteArray();
// Call the define method
try {
if (defineMethod == null) {
Method defined = ClassLoader.class.getDeclaredMethod("defineClass",
new Class<?>[] { String.class, byte[].class, int.class, int.class });
// Awesome. Now, create and return it.
defined.setAccessible(true);
defineMethod = defined;
}
@SuppressWarnings("rawtypes")
Class clazz = (Class) defineMethod.invoke(loader, null, data, 0, data.length);
// DEBUG CODE: Print the content of the generated class.
//org.objectweb.asm.ClassReader cr = new org.objectweb.asm.ClassReader(data);
//cr.accept(new ASMifierClassVisitor(new PrintWriter(System.out)), 0);
return clazz;
} catch (SecurityException e) {
throw new RuntimeException("Cannot use reflection to dynamically load a class.", e);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Incompatible JVM.", e);
} catch (IllegalArgumentException e) {
throw new IllegalStateException("Cannot call defineMethod - wrong JVM?", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Security limitation! Cannot dynamically load class.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error occured in code generator.", e);
}
}
/**
* Determine if at least one of the given fields is public.
* @param fields - field to test.
* @return TRUE if one or more field is publically accessible, FALSE otherwise.
*/
private boolean isAnyPublic(List<Field> fields) {
// Are any of the fields public?
for (int i = 0; i < fields.size(); i++) {
if (isPublic(fields.get(i))) {
return true;
}
}
return false;
}
private boolean isPublic(Field field) {
return Modifier.isPublic(field.getModifiers());
}
private void createFields(ClassWriter cw, String targetSignature) {
FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null);
typedField.visitEnd();
}
private void createWriteMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";";
String methodSignature = "(ITTField;)L" + SUPER_CLASS + "<TTField;>;";
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature,
new String[] { FIELD_EXCEPTION_CLASS });
BoxingHelper boxingHelper = new BoxingHelper(mv);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
mv.visitVarInsn(Opcodes.ASTORE, 3);
mv.visitVarInsn(Opcodes.ILOAD, 1);
// The last label is for the default switch
Label[] labels = new Label[fields.size()];
Label errorLabel = new Label();
Label returnLabel = new Label();
// Generate labels
for (int i = 0; i < fields.size(); i++) {
labels[i] = new Label();
}
mv.visitTableSwitchInsn(0, labels.length - 1, errorLabel, labels);
for (int i = 0; i < fields.size(); i++) {
Class<?> outputType = fields.get(i).getType();
Class<?> inputType = PrimitiveUtils.wrap(outputType);
String typeDescriptor = Type.getDescriptor(outputType);
String inputPath = inputType.getName().replace('.', '/');
mv.visitLabel(labels[i]);
// Push the compare object
if (i == 0)
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
else
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
// Only write to public fields
if (isPublic(fields.get(i))) {
mv.visitVarInsn(Opcodes.ALOAD, 3);
mv.visitVarInsn(Opcodes.ALOAD, 2);
if (!PrimitiveUtils.isPrimitive(outputType))
mv.visitTypeInsn(Opcodes.CHECKCAST, inputPath);
else
boxingHelper.unbox(Type.getType(outputType));
mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, fields.get(i).getName(), typeDescriptor);
} else {
// Use reflection. We don't have a choice, unfortunately.
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "write", "(ILjava/lang/Object;)L" + SUPER_CLASS + ";");
mv.visitInsn(Opcodes.POP);
}
mv.visitJumpInsn(Opcodes.GOTO, returnLabel);
}
mv.visitLabel(errorLabel);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
mv.visitInsn(Opcodes.DUP);
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn("Invalid index ");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(Opcodes.ATHROW);
mv.visitLabel(returnLabel);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(5, 4);
mv.visitEnd();
}
private void createReadMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null,
new String[] { "com/comphenix/protocol/reflect/FieldAccessException" });
BoxingHelper boxingHelper = new BoxingHelper(mv);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
mv.visitVarInsn(Opcodes.ASTORE, 2);
mv.visitVarInsn(Opcodes.ILOAD, 1);
// The last label is for the default switch
Label[] labels = new Label[fields.size()];
Label errorLabel = new Label();
// Generate labels
for (int i = 0; i < fields.size(); i++) {
labels[i] = new Label();
}
mv.visitTableSwitchInsn(0, fields.size() - 1, errorLabel, labels);
for (int i = 0; i < fields.size(); i++) {
Class<?> outputType = fields.get(i).getType();
String typeDescriptor = Type.getDescriptor(outputType);
mv.visitLabel(labels[i]);
// Push the compare object
if (i == 0)
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
else
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
// Note that byte code cannot access non-public fields
if (isPublic(fields.get(i))) {
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitFieldInsn(Opcodes.GETFIELD, targetName, fields.get(i).getName(), typeDescriptor);
boxingHelper.box(Type.getType(outputType));
} else {
// We have to use reflection for private and protected fields.
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "read", "(I)Ljava/lang/Object;");
}
mv.visitInsn(Opcodes.ARETURN);
}
mv.visitLabel(errorLabel);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
mv.visitInsn(Opcodes.DUP);
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn("Invalid index ");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(Opcodes.ATHROW);
mv.visitMaxs(5, 3);
mv.visitEnd();
}
private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
"(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V",
"(L" + SUPER_CLASS + "<TTField;>;L" + SUPER_CLASS + ";)V", null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "<init>", "()V");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, PACKAGE_NAME + "/" + className, "initialize", "(L" + SUPER_CLASS + ";)V");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;");
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
mv.visitTypeInsn(Opcodes.CHECKCAST, targetName);
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 3);
mv.visitEnd();
}
}
/*
* 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.compiler;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.comphenix.protocol.reflect.PrimitiveUtils;
import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.base.Objects;
import net.sf.cglib.asm.*;
// public class CompiledStructureModifierPacket20<TField> extends CompiledStructureModifier<TField> {
//
// private Packet20NamedEntitySpawn typedTarget;
//
// public CompiledStructureModifierPacket20(StructureModifier<TField> other, StructureCompiler compiler) {
// super();
// initialize(other);
// this.target = other.getTarget();
// this.typedTarget = (Packet20NamedEntitySpawn) target;
// this.compiler = compiler;
// }
//
// @Override
// protected Object readGenerated(int fieldIndex) throws FieldAccessException {
//
// Packet20NamedEntitySpawn target = typedTarget;
//
// switch (fieldIndex) {
// case 0: return (Object) target.a;
// case 1: return (Object) target.b;
// case 2: return (Object) target.c;
// case 3: return super.read(fieldIndex);
// case 4: return super.read(fieldIndex);
// case 5: return (Object) target.f;
// case 6: return (Object) target.g;
// case 7: return (Object) target.h;
// default:
// throw new FieldAccessException("Invalid index " + fieldIndex);
// }
// }
//
// @Override
// protected StructureModifier<TField> writeGenerated(int index, Object value) throws FieldAccessException {
//
// Packet20NamedEntitySpawn target = typedTarget;
//
// switch (index) {
// case 0: target.a = (Integer) value; break;
// case 1: target.b = (String) value; break;
// case 2: target.c = (Integer) value; break;
// case 3: target.d = (Integer) value; break;
// case 4: super.write(index, value); break;
// case 5: super.write(index, value); break;
// case 6: target.g = (Byte) value; break;
// case 7: target.h = (Integer) value; break;
// default:
// throw new FieldAccessException("Invalid index " + index);
// }
//
// // Chaining
// return this;
// }
// }
/**
* Represents a StructureModifier compiler.
*
* @author Kristian
*/
public final class StructureCompiler {
// Used to store generated classes of different types
@SuppressWarnings("rawtypes")
private class StructureKey {
private Class targetType;
private Class fieldType;
public StructureKey(Class targetType, Class fieldType) {
this.targetType = targetType;
this.fieldType = fieldType;
}
@Override
public int hashCode() {
return Objects.hashCode(targetType, fieldType);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof StructureKey) {
StructureKey other = (StructureKey) obj;
return Objects.equal(targetType, other.targetType) &&
Objects.equal(fieldType, other.fieldType);
}
return false;
}
}
// Used to load classes
private volatile static Method defineMethod;
@SuppressWarnings("rawtypes")
private Map<StructureKey, Class> compiledCache = new HashMap<StructureKey, Class>();
// The class loader we'll store our classes
private ClassLoader loader;
// References to other classes
private static String PACKAGE_NAME = "com/comphenix/protocol/reflect/compiler";
private static String SUPER_CLASS = "com/comphenix/protocol/reflect/StructureModifier";
private static String COMPILED_CLASS = PACKAGE_NAME + "/CompiledStructureModifier";
private static String FIELD_EXCEPTION_CLASS = "com/comphenix/protocol/reflect/FieldAccessException";
/**
* Construct a structure compiler.
* @param loader - main class loader.
*/
StructureCompiler(ClassLoader loader) {
this.loader = loader;
}
/**
* Compiles the given structure modifier.
* <p>
* WARNING: Do NOT call this method in the main thread. Compiling may easily take 10 ms, which is already
* over 1/4 of a tick (50 ms). Let the background thread automatically compile the structure modifiers instead.
* @param source - structure modifier to compile.
* @return A compiled structure modifier.
*/
@SuppressWarnings("unchecked")
public synchronized <TField> StructureModifier<TField> compile(StructureModifier<TField> source) {
// We cannot optimize a structure modifier with no public fields
if (!isAnyPublic(source.getFields())) {
return source;
}
StructureKey key = new StructureKey(source.getTargetType(), source.getFieldType());
Class<?> compiledClass = compiledCache.get(key);
if (!compiledCache.containsKey(key)) {
compiledClass = generateClass(source);
compiledCache.put(key, compiledClass);
}
// Next, create an instance of this class
try {
return (StructureModifier<TField>) compiledClass.getConstructor(
StructureModifier.class, StructureCompiler.class).
newInstance(source, this);
} catch (IllegalArgumentException e) {
throw new IllegalStateException("Used invalid parameters in instance creation", e);
} catch (SecurityException e) {
throw new RuntimeException("Security limitation!", e);
} catch (InstantiationException e) {
throw new RuntimeException("Error occured while instancing generated class.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Security limitation! Cannot create instance of dynamic class.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error occured while instancing generated class.", e);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Cannot happen.", e);
}
}
private <TField> Class<?> generateClass(StructureModifier<TField> source) {
ClassWriter cw = new ClassWriter(0);
@SuppressWarnings("rawtypes")
Class targetType = source.getTargetType();
String className = "CompiledStructure$" + targetType.getSimpleName() + source.getFieldType().getSimpleName();
String targetSignature = Type.getDescriptor(targetType);
String targetName = targetType.getName().replace('.', '/');
try {
// This class might have been generated before. Try to load it.
Class<?> before = loader.loadClass(PACKAGE_NAME.replace('/', '.') + "." + className);
if (before != null)
return before;
} catch (ClassNotFoundException e) {
// That's ok.
}
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className,
"<TField:Ljava/lang/Object;>L" + COMPILED_CLASS + "<TTField;>;",
COMPILED_CLASS, null);
createFields(cw, targetSignature);
createConstructor(cw, className, targetSignature, targetName);
createReadMethod(cw, className, source.getFields(), targetSignature, targetName);
createWriteMethod(cw, className, source.getFields(), targetSignature, targetName);
cw.visitEnd();
byte[] data = cw.toByteArray();
// Call the define method
try {
if (defineMethod == null) {
Method defined = ClassLoader.class.getDeclaredMethod("defineClass",
new Class<?>[] { String.class, byte[].class, int.class, int.class });
// Awesome. Now, create and return it.
defined.setAccessible(true);
defineMethod = defined;
}
@SuppressWarnings("rawtypes")
Class clazz = (Class) defineMethod.invoke(loader, null, data, 0, data.length);
// DEBUG CODE: Print the content of the generated class.
//org.objectweb.asm.ClassReader cr = new org.objectweb.asm.ClassReader(data);
//cr.accept(new ASMifierClassVisitor(new PrintWriter(System.out)), 0);
return clazz;
} catch (SecurityException e) {
throw new RuntimeException("Cannot use reflection to dynamically load a class.", e);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Incompatible JVM.", e);
} catch (IllegalArgumentException e) {
throw new IllegalStateException("Cannot call defineMethod - wrong JVM?", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Security limitation! Cannot dynamically load class.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error occured in code generator.", e);
}
}
/**
* Determine if at least one of the given fields is public.
* @param fields - field to test.
* @return TRUE if one or more field is publically accessible, FALSE otherwise.
*/
private boolean isAnyPublic(List<Field> fields) {
// Are any of the fields public?
for (int i = 0; i < fields.size(); i++) {
if (isPublic(fields.get(i))) {
return true;
}
}
return false;
}
private boolean isPublic(Field field) {
return Modifier.isPublic(field.getModifiers());
}
private void createFields(ClassWriter cw, String targetSignature) {
FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null);
typedField.visitEnd();
}
private void createWriteMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";";
String methodSignature = "(ITTField;)L" + SUPER_CLASS + "<TTField;>;";
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature,
new String[] { FIELD_EXCEPTION_CLASS });
BoxingHelper boxingHelper = new BoxingHelper(mv);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
mv.visitVarInsn(Opcodes.ASTORE, 3);
mv.visitVarInsn(Opcodes.ILOAD, 1);
// The last label is for the default switch
Label[] labels = new Label[fields.size()];
Label errorLabel = new Label();
Label returnLabel = new Label();
// Generate labels
for (int i = 0; i < fields.size(); i++) {
labels[i] = new Label();
}
mv.visitTableSwitchInsn(0, labels.length - 1, errorLabel, labels);
for (int i = 0; i < fields.size(); i++) {
Class<?> outputType = fields.get(i).getType();
Class<?> inputType = PrimitiveUtils.wrap(outputType);
String typeDescriptor = Type.getDescriptor(outputType);
String inputPath = inputType.getName().replace('.', '/');
mv.visitLabel(labels[i]);
// Push the compare object
if (i == 0)
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
else
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
// Only write to public fields
if (isPublic(fields.get(i))) {
mv.visitVarInsn(Opcodes.ALOAD, 3);
mv.visitVarInsn(Opcodes.ALOAD, 2);
if (!PrimitiveUtils.isPrimitive(outputType))
mv.visitTypeInsn(Opcodes.CHECKCAST, inputPath);
else
boxingHelper.unbox(Type.getType(outputType));
mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, fields.get(i).getName(), typeDescriptor);
} else {
// Use reflection. We don't have a choice, unfortunately.
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "write", "(ILjava/lang/Object;)L" + SUPER_CLASS + ";");
mv.visitInsn(Opcodes.POP);
}
mv.visitJumpInsn(Opcodes.GOTO, returnLabel);
}
mv.visitLabel(errorLabel);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
mv.visitInsn(Opcodes.DUP);
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn("Invalid index ");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(Opcodes.ATHROW);
mv.visitLabel(returnLabel);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(5, 4);
mv.visitEnd();
}
private void createReadMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null,
new String[] { "com/comphenix/protocol/reflect/FieldAccessException" });
BoxingHelper boxingHelper = new BoxingHelper(mv);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
mv.visitVarInsn(Opcodes.ASTORE, 2);
mv.visitVarInsn(Opcodes.ILOAD, 1);
// The last label is for the default switch
Label[] labels = new Label[fields.size()];
Label errorLabel = new Label();
// Generate labels
for (int i = 0; i < fields.size(); i++) {
labels[i] = new Label();
}
mv.visitTableSwitchInsn(0, fields.size() - 1, errorLabel, labels);
for (int i = 0; i < fields.size(); i++) {
Class<?> outputType = fields.get(i).getType();
String typeDescriptor = Type.getDescriptor(outputType);
mv.visitLabel(labels[i]);
// Push the compare object
if (i == 0)
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
else
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
// Note that byte code cannot access non-public fields
if (isPublic(fields.get(i))) {
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitFieldInsn(Opcodes.GETFIELD, targetName, fields.get(i).getName(), typeDescriptor);
boxingHelper.box(Type.getType(outputType));
} else {
// We have to use reflection for private and protected fields.
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "read", "(I)Ljava/lang/Object;");
}
mv.visitInsn(Opcodes.ARETURN);
}
mv.visitLabel(errorLabel);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
mv.visitInsn(Opcodes.DUP);
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn("Invalid index ");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(Opcodes.ATHROW);
mv.visitMaxs(5, 3);
mv.visitEnd();
}
private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
"(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V",
"(L" + SUPER_CLASS + "<TTField;>;L" + SUPER_CLASS + ";)V", null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "<init>", "()V");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, PACKAGE_NAME + "/" + className, "initialize", "(L" + SUPER_CLASS + ";)V");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;");
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
mv.visitTypeInsn(Opcodes.CHECKCAST, targetName);
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 3);
mv.visitEnd();
}
}
@@ -1,51 +1,68 @@
package com.comphenix.protocol.reflect.instances;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.annotation.Nullable;
/**
* Provides simple constructors for collection interfaces.
* @author Kristian
*/
public class CollectionGenerator implements InstanceProvider {
/**
* Shared instance of this generator.
*/
public static CollectionGenerator INSTANCE = new CollectionGenerator();
@Override
public Object create(@Nullable Class<?> type) {
// Standard collection types
if (type.isInterface()) {
if (type.equals(Collection.class) || type.equals(List.class))
return new ArrayList<Object>();
else if (type.equals(Set.class))
return new HashSet<Object>();
else if (type.equals(Map.class))
return new HashMap<Object, Object>();
else if (type.equals(SortedSet.class))
return new TreeSet<Object>();
else if (type.equals(SortedMap.class))
return new TreeMap<Object, Object>();
else if (type.equals(Queue.class))
return new LinkedList<Object>();
}
// Cannot provide an instance
return null;
}
/*
* 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.instances;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.annotation.Nullable;
/**
* Provides simple constructors for collection interfaces.
* @author Kristian
*/
public class CollectionGenerator implements InstanceProvider {
/**
* Shared instance of this generator.
*/
public static final CollectionGenerator INSTANCE = new CollectionGenerator();
@Override
public Object create(@Nullable Class<?> type) {
// Standard collection types
if (type.isInterface()) {
if (type.equals(Collection.class) || type.equals(List.class))
return new ArrayList<Object>();
else if (type.equals(Set.class))
return new HashSet<Object>();
else if (type.equals(Map.class))
return new HashMap<Object, Object>();
else if (type.equals(SortedSet.class))
return new TreeSet<Object>();
else if (type.equals(SortedMap.class))
return new TreeMap<Object, Object>();
else if (type.equals(Queue.class))
return new LinkedList<Object>();
}
// Cannot provide an instance
return null;
}
}
@@ -1,330 +1,330 @@
/*
* 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.instances;
import java.lang.reflect.Constructor;
import java.util.*;
import net.sf.cglib.proxy.Enhancer;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
/**
* Used to construct default instances of any type.
* @author Kristian
*
*/
public class DefaultInstances {
/**
* Standard default instance provider.
*/
public static DefaultInstances DEFAULT = DefaultInstances.fromArray(
PrimitiveGenerator.INSTANCE, CollectionGenerator.INSTANCE);
/**
* The maximum height of the hierachy of creates types. Used to prevent cycles.
*/
private int maximumRecursion = 20;
/**
* Ordered list of instance provider, from highest priority to lowest.
*/
private ImmutableList<InstanceProvider> registered;
/**
* Whether or not the constructor must be non-null.
*/
private boolean nonNull;
/**
* Construct a default instance generator using the given instance providers.
* @param registered - list of instance providers.
*/
public DefaultInstances(ImmutableList<InstanceProvider> registered) {
this.registered = registered;
}
/**
* Copy a given instance provider.
* @param other - instance provider to copy.
*/
public DefaultInstances(DefaultInstances other) {
this.nonNull = other.nonNull;
this.maximumRecursion = other.maximumRecursion;
this.registered = other.registered;
}
/**
* Construct a default instance generator using the given instance providers.
* @param instaceProviders - array of instance providers.
*/
public DefaultInstances(InstanceProvider... instaceProviders) {
this(ImmutableList.copyOf(instaceProviders));
}
/**
* Construct a default instance generator using the given instance providers.
* @param instaceProviders - array of instance providers.
* @return An default instance generator.
*/
public static DefaultInstances fromArray(InstanceProvider... instaceProviders) {
return new DefaultInstances(ImmutableList.copyOf(instaceProviders));
}
/**
* Retrieves a immutable list of every default object providers that generates instances.
* @return Table of instance providers.
*/
public ImmutableList<InstanceProvider> getRegistered() {
return registered;
}
/**
* Retrieve whether or not the constructor's parameters must be non-null.
* @return TRUE if they must be non-null, FALSE otherwise.
*/
public boolean isNonNull() {
return nonNull;
}
/**
* Set whether or not the constructor's parameters must be non-null.
* @param nonNull - TRUE if they must be non-null, FALSE otherwise.
*/
public void setNonNull(boolean nonNull) {
this.nonNull = nonNull;
}
/**
* Retrieve the the maximum height of the hierachy of creates types.
* @return Maximum height.
*/
public int getMaximumRecursion() {
return maximumRecursion;
}
/**
* Set the maximum height of the hierachy of creates types. Used to prevent cycles.
* @param maximumRecursion - maximum recursion height.
*/
public void setMaximumRecursion(int maximumRecursion) {
if (maximumRecursion < 1)
throw new IllegalArgumentException("Maxmimum recursion height must be one or higher.");
this.maximumRecursion = maximumRecursion;
}
/**
* Retrieves a default instance or value that is assignable to this type.
* <p>
* This includes, but isn't limited too:
* <ul>
* <li>Primitive types. Returns either zero or null.</li>
* <li>Primitive wrappers.</li>
* <li>String types. Returns an empty string.</li>
* <li>Arrays. Returns a zero-length array of the same type.</li>
* <li>Enums. Returns the first declared element.</li>
* <li>Collection interfaces, such as List and Set. Returns the most appropriate empty container.</li>
* <li>Any type with a public constructor that has parameters with defaults.</li>
* </ul>
* </ul>
* @param type - the type to construct a default value.
* @return A default value/instance, or NULL if not possible.
*/
public <T> T getDefault(Class<T> type) {
return getDefaultInternal(type, registered, 0);
}
/**
* Retrieve the constructor with the fewest number of parameters.
* @param type - type to construct.
* @return A constructor with the fewest number of parameters, or NULL if the type has no constructors.
*/
public <T> Constructor<T> getMinimumConstructor(Class<T> type) {
return getMinimumConstructor(type, registered, 0);
}
@SuppressWarnings("unchecked")
private <T> Constructor<T> getMinimumConstructor(Class<T> type, List<InstanceProvider> providers, int recursionLevel) {
Constructor<T> minimum = null;
int lastCount = Integer.MAX_VALUE;
// Find the constructor with the fewest parameters
for (Constructor<?> candidate : type.getConstructors()) {
Class<?>[] types = candidate.getParameterTypes();
// Note that we don't allow recursive types - that is, types that
// require itself in the constructor.
if (types.length < lastCount) {
if (!contains(types, type)) {
if (nonNull) {
// Make sure all of these types are non-null
if (isAnyNull(types, providers, recursionLevel)) {
continue;
}
}
minimum = (Constructor<T>) candidate;
lastCount = types.length;
// Don't loop again if we've already found the best possible constructor
if (lastCount == 0)
break;
}
}
}
return minimum;
}
/**
* Determine if any of the given types will be NULL once created.
* <p>
* Recursion level is the number of times the default method has been called.
* @param types - types to check.
* @param providers - instance providers.
* @param recursionLevel - current recursion level.
* @return
*/
private boolean isAnyNull(Class<?>[] types, List<InstanceProvider> providers, int recursionLevel) {
// Just check if any of them are NULL
for (Class<?> type : types) {
if (getDefaultInternal(type, providers, recursionLevel) == null) {
System.out.println(type.getName() + " is NULL!");
return true;
}
}
return false;
}
/**
* Retrieves a default instance or value that is assignable to this type.
* <p>
* This includes, but isn't limited too:
* <ul>
* <li>Primitive types. Returns either zero or null.</li>
* <li>Primitive wrappers.</li>
* <li>String types. Returns an empty string.</li>
* <li>Arrays. Returns a zero-length array of the same type.</li>
* <li>Enums. Returns the first declared element.</li>
* <li>Collection interfaces, such as List and Set. Returns the most appropriate empty container.</li>
* <li>Any type with a public constructor that has parameters with defaults.</li>
* </ul>
* </ul>
* @param type - the type to construct a default value.
* @param providers - instance providers used during the construction.
* @return A default value/instance, or NULL if not possible.
*/
public <T> T getDefault(Class<T> type, List<InstanceProvider> providers) {
return getDefaultInternal(type, providers, 0);
}
@SuppressWarnings("unchecked")
private <T> T getDefaultInternal(Class<T> type, List<InstanceProvider> providers, int recursionLevel) {
// The instance providiers should protect themselves against recursion
for (InstanceProvider generator : providers) {
Object value = generator.create(type);
if (value != null)
return (T) value;
}
// Guard against recursion
if (recursionLevel >= maximumRecursion) {
return null;
}
Constructor<T> minimum = getMinimumConstructor(type, providers, recursionLevel + 1);
// Create the type with this constructor using default values. This might fail, though.
try {
if (minimum != null) {
int parameterCount = minimum.getParameterTypes().length;
Object[] params = new Object[parameterCount];
Class<?>[] types = minimum.getParameterTypes();
// Fill out
for (int i = 0; i < parameterCount; i++) {
params[i] = getDefaultInternal(types[i], providers, recursionLevel + 1);
// Did we break the non-null contract?
if (params[i] == null && nonNull) {
return null;
}
}
return createInstance(type, minimum, types, params);
}
} catch (Exception e) {
// Nope, we couldn't create this type
}
// No suitable default value could be found
return null;
}
/**
* Construct default instances using the CGLIB enhancer object instead.
* @param enhancer - a CGLIB enhancer to use.
* @return A default instance generator that uses the CGLIB enhancer.
*/
public DefaultInstances forEnhancer(Enhancer enhancer) {
final Enhancer ex = enhancer;
return new DefaultInstances(this) {
@SuppressWarnings("unchecked")
@Override
protected <T> T createInstance(Class<T> type, Constructor<T> constructor, Class<?>[] types, Object[] params) {
// Use the enhancer instead
return (T) ex.create(types, params);
}
};
}
/**
* Used by the default instance provider to create a class from a given constructor.
* The default method uses reflection.
* @param type - the type to create.
* @param constructor - the constructor to use.
* @param types - type of each parameter in order.
* @param params - value of each parameter in order.
* @return The constructed instance.
*/
protected <T> T createInstance(Class<T> type, Constructor<T> constructor, Class<?>[] types, Object[] params) {
try {
return (T) constructor.newInstance(params);
} catch (Exception e) {
// Cannot create it
return null;
}
}
// We avoid Apache's utility methods to stay backwards compatible
protected <T> boolean contains(T[] elements, T elementToFind) {
// Search for the given element in the array
for (T element : elements) {
if (Objects.equal(elementToFind, element))
return true;
}
return false;
}
}
/*
* 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.instances;
import java.lang.reflect.Constructor;
import java.util.*;
import net.sf.cglib.proxy.Enhancer;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
/**
* Used to construct default instances of any type.
* @author Kristian
*
*/
public class DefaultInstances {
/**
* Standard default instance provider.
*/
public static final DefaultInstances DEFAULT = DefaultInstances.fromArray(
PrimitiveGenerator.INSTANCE, CollectionGenerator.INSTANCE);
/**
* The maximum height of the hierachy of creates types. Used to prevent cycles.
*/
private int maximumRecursion = 20;
/**
* Ordered list of instance provider, from highest priority to lowest.
*/
private ImmutableList<InstanceProvider> registered;
/**
* Whether or not the constructor must be non-null.
*/
private boolean nonNull;
/**
* Construct a default instance generator using the given instance providers.
* @param registered - list of instance providers.
*/
public DefaultInstances(ImmutableList<InstanceProvider> registered) {
this.registered = registered;
}
/**
* Copy a given instance provider.
* @param other - instance provider to copy.
*/
public DefaultInstances(DefaultInstances other) {
this.nonNull = other.nonNull;
this.maximumRecursion = other.maximumRecursion;
this.registered = other.registered;
}
/**
* Construct a default instance generator using the given instance providers.
* @param instaceProviders - array of instance providers.
*/
public DefaultInstances(InstanceProvider... instaceProviders) {
this(ImmutableList.copyOf(instaceProviders));
}
/**
* Construct a default instance generator using the given instance providers.
* @param instaceProviders - array of instance providers.
* @return An default instance generator.
*/
public static DefaultInstances fromArray(InstanceProvider... instaceProviders) {
return new DefaultInstances(ImmutableList.copyOf(instaceProviders));
}
/**
* Retrieves a immutable list of every default object providers that generates instances.
* @return Table of instance providers.
*/
public ImmutableList<InstanceProvider> getRegistered() {
return registered;
}
/**
* Retrieve whether or not the constructor's parameters must be non-null.
* @return TRUE if they must be non-null, FALSE otherwise.
*/
public boolean isNonNull() {
return nonNull;
}
/**
* Set whether or not the constructor's parameters must be non-null.
* @param nonNull - TRUE if they must be non-null, FALSE otherwise.
*/
public void setNonNull(boolean nonNull) {
this.nonNull = nonNull;
}
/**
* Retrieve the the maximum height of the hierachy of creates types.
* @return Maximum height.
*/
public int getMaximumRecursion() {
return maximumRecursion;
}
/**
* Set the maximum height of the hierachy of creates types. Used to prevent cycles.
* @param maximumRecursion - maximum recursion height.
*/
public void setMaximumRecursion(int maximumRecursion) {
if (maximumRecursion < 1)
throw new IllegalArgumentException("Maxmimum recursion height must be one or higher.");
this.maximumRecursion = maximumRecursion;
}
/**
* Retrieves a default instance or value that is assignable to this type.
* <p>
* This includes, but isn't limited too:
* <ul>
* <li>Primitive types. Returns either zero or null.</li>
* <li>Primitive wrappers.</li>
* <li>String types. Returns an empty string.</li>
* <li>Arrays. Returns a zero-length array of the same type.</li>
* <li>Enums. Returns the first declared element.</li>
* <li>Collection interfaces, such as List and Set. Returns the most appropriate empty container.</li>
* <li>Any type with a public constructor that has parameters with defaults.</li>
* </ul>
* </ul>
* @param type - the type to construct a default value.
* @return A default value/instance, or NULL if not possible.
*/
public <T> T getDefault(Class<T> type) {
return getDefaultInternal(type, registered, 0);
}
/**
* Retrieve the constructor with the fewest number of parameters.
* @param type - type to construct.
* @return A constructor with the fewest number of parameters, or NULL if the type has no constructors.
*/
public <T> Constructor<T> getMinimumConstructor(Class<T> type) {
return getMinimumConstructor(type, registered, 0);
}
@SuppressWarnings("unchecked")
private <T> Constructor<T> getMinimumConstructor(Class<T> type, List<InstanceProvider> providers, int recursionLevel) {
Constructor<T> minimum = null;
int lastCount = Integer.MAX_VALUE;
// Find the constructor with the fewest parameters
for (Constructor<?> candidate : type.getConstructors()) {
Class<?>[] types = candidate.getParameterTypes();
// Note that we don't allow recursive types - that is, types that
// require itself in the constructor.
if (types.length < lastCount) {
if (!contains(types, type)) {
if (nonNull) {
// Make sure all of these types are non-null
if (isAnyNull(types, providers, recursionLevel)) {
continue;
}
}
minimum = (Constructor<T>) candidate;
lastCount = types.length;
// Don't loop again if we've already found the best possible constructor
if (lastCount == 0)
break;
}
}
}
return minimum;
}
/**
* Determine if any of the given types will be NULL once created.
* <p>
* Recursion level is the number of times the default method has been called.
* @param types - types to check.
* @param providers - instance providers.
* @param recursionLevel - current recursion level.
* @return TRUE if any of the types will return NULL, FALSE otherwise.
*/
private boolean isAnyNull(Class<?>[] types, List<InstanceProvider> providers, int recursionLevel) {
// Just check if any of them are NULL
for (Class<?> type : types) {
if (getDefaultInternal(type, providers, recursionLevel) == null) {
System.out.println(type.getName() + " is NULL!");
return true;
}
}
return false;
}
/**
* Retrieves a default instance or value that is assignable to this type.
* <p>
* This includes, but isn't limited too:
* <ul>
* <li>Primitive types. Returns either zero or null.</li>
* <li>Primitive wrappers.</li>
* <li>String types. Returns an empty string.</li>
* <li>Arrays. Returns a zero-length array of the same type.</li>
* <li>Enums. Returns the first declared element.</li>
* <li>Collection interfaces, such as List and Set. Returns the most appropriate empty container.</li>
* <li>Any type with a public constructor that has parameters with defaults.</li>
* </ul>
* </ul>
* @param type - the type to construct a default value.
* @param providers - instance providers used during the construction.
* @return A default value/instance, or NULL if not possible.
*/
public <T> T getDefault(Class<T> type, List<InstanceProvider> providers) {
return getDefaultInternal(type, providers, 0);
}
@SuppressWarnings("unchecked")
private <T> T getDefaultInternal(Class<T> type, List<InstanceProvider> providers, int recursionLevel) {
// The instance providiers should protect themselves against recursion
for (InstanceProvider generator : providers) {
Object value = generator.create(type);
if (value != null)
return (T) value;
}
// Guard against recursion
if (recursionLevel >= maximumRecursion) {
return null;
}
Constructor<T> minimum = getMinimumConstructor(type, providers, recursionLevel + 1);
// Create the type with this constructor using default values. This might fail, though.
try {
if (minimum != null) {
int parameterCount = minimum.getParameterTypes().length;
Object[] params = new Object[parameterCount];
Class<?>[] types = minimum.getParameterTypes();
// Fill out
for (int i = 0; i < parameterCount; i++) {
params[i] = getDefaultInternal(types[i], providers, recursionLevel + 1);
// Did we break the non-null contract?
if (params[i] == null && nonNull) {
return null;
}
}
return createInstance(type, minimum, types, params);
}
} catch (Exception e) {
// Nope, we couldn't create this type
}
// No suitable default value could be found
return null;
}
/**
* Construct default instances using the CGLIB enhancer object instead.
* @param enhancer - a CGLIB enhancer to use.
* @return A default instance generator that uses the CGLIB enhancer.
*/
public DefaultInstances forEnhancer(Enhancer enhancer) {
final Enhancer ex = enhancer;
return new DefaultInstances(this) {
@SuppressWarnings("unchecked")
@Override
protected <T> T createInstance(Class<T> type, Constructor<T> constructor, Class<?>[] types, Object[] params) {
// Use the enhancer instead
return (T) ex.create(types, params);
}
};
}
/**
* Used by the default instance provider to create a class from a given constructor.
* The default method uses reflection.
* @param type - the type to create.
* @param constructor - the constructor to use.
* @param types - type of each parameter in order.
* @param params - value of each parameter in order.
* @return The constructed instance.
*/
protected <T> T createInstance(Class<T> type, Constructor<T> constructor, Class<?>[] types, Object[] params) {
try {
return (T) constructor.newInstance(params);
} catch (Exception e) {
// Cannot create it
return null;
}
}
// We avoid Apache's utility methods to stay backwards compatible
protected <T> boolean contains(T[] elements, T elementToFind) {
// Search for the given element in the array
for (T element : elements) {
if (Objects.equal(elementToFind, element))
return true;
}
return false;
}
}
@@ -1,111 +1,128 @@
package com.comphenix.protocol.reflect.instances;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
/**
* Provides instance constructors using a list of existing values.
* <p>
* Only one instance per individual class.
* @author Kristian
*/
public class ExistingGenerator implements InstanceProvider {
private Map<String, Object> existingValues = new HashMap<String, Object>();
private ExistingGenerator() {
// Only accessible to the constructors
}
/**
* Automatically create an instance provider from a objects public and private fields.
* <p>
* If two or more fields share the same type, the last declared non-null field will take
* precedent.
* @param object - object to create an instance generator from.
* @return The instance generator.
*/
public static ExistingGenerator fromObjectFields(Object object) {
if (object == null)
throw new IllegalArgumentException("Object cannot be NULL.");
return fromObjectFields(object, object.getClass());
}
/**
* Automatically create an instance provider from a objects public and private fields.
* <p>
* If two or more fields share the same type, the last declared non-null field will take
* precedent.
* @param object - object to create an instance generator from.
* @param type - the type to cast the object.
* @return The instance generator.
*/
public static ExistingGenerator fromObjectFields(Object object, Class<?> type) {
ExistingGenerator generator = new ExistingGenerator();
// Possible errors
if (object == null)
throw new IllegalArgumentException("Object cannot be NULL.");
if (type == null)
throw new IllegalArgumentException("Type cannot be NULL.");
if (!type.isAssignableFrom(object.getClass()))
throw new IllegalArgumentException("Type must be a superclass or be the same type.");
// Read instances from every field.
for (Field field : FuzzyReflection.fromClass(type, true).getFields()) {
try {
Object value = FieldUtils.readField(field, object, true);
// Use the type of the field, not the object itself
if (value != null)
generator.addObject(field.getType(), value);
} catch (Exception e) {
// Yes, swallow it. No, really.
}
}
return generator;
}
/**
* Create an instance generator from a pre-defined array of values.
* @param values - values to provide.
* @return An instance provider that uses these values.
*/
public static ExistingGenerator fromObjectArray(Object[] values) {
ExistingGenerator generator = new ExistingGenerator();
for (Object value : values)
generator.addObject(value);
return generator;
}
private void addObject(Object value) {
if (value == null)
throw new IllegalArgumentException("Value cannot be NULL.");
existingValues.put(value.getClass().getName(), value);
}
private void addObject(Class<?> type, Object value) {
existingValues.put(type.getName(), value);
}
@Override
public Object create(@Nullable Class<?> type) {
Object value = existingValues.get(type.getName());
// NULL values indicate that the generator failed
return value;
}
}
/*
* 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.instances;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
/**
* Provides instance constructors using a list of existing values.
* <p>
* Only one instance per individual class.
* @author Kristian
*/
public class ExistingGenerator implements InstanceProvider {
private Map<String, Object> existingValues = new HashMap<String, Object>();
private ExistingGenerator() {
// Only accessible to the constructors
}
/**
* Automatically create an instance provider from a objects public and private fields.
* <p>
* If two or more fields share the same type, the last declared non-null field will take
* precedent.
* @param object - object to create an instance generator from.
* @return The instance generator.
*/
public static ExistingGenerator fromObjectFields(Object object) {
if (object == null)
throw new IllegalArgumentException("Object cannot be NULL.");
return fromObjectFields(object, object.getClass());
}
/**
* Automatically create an instance provider from a objects public and private fields.
* <p>
* If two or more fields share the same type, the last declared non-null field will take
* precedent.
* @param object - object to create an instance generator from.
* @param type - the type to cast the object.
* @return The instance generator.
*/
public static ExistingGenerator fromObjectFields(Object object, Class<?> type) {
ExistingGenerator generator = new ExistingGenerator();
// Possible errors
if (object == null)
throw new IllegalArgumentException("Object cannot be NULL.");
if (type == null)
throw new IllegalArgumentException("Type cannot be NULL.");
if (!type.isAssignableFrom(object.getClass()))
throw new IllegalArgumentException("Type must be a superclass or be the same type.");
// Read instances from every field.
for (Field field : FuzzyReflection.fromClass(type, true).getFields()) {
try {
Object value = FieldUtils.readField(field, object, true);
// Use the type of the field, not the object itself
if (value != null)
generator.addObject(field.getType(), value);
} catch (Exception e) {
// Yes, swallow it. No, really.
}
}
return generator;
}
/**
* Create an instance generator from a pre-defined array of values.
* @param values - values to provide.
* @return An instance provider that uses these values.
*/
public static ExistingGenerator fromObjectArray(Object[] values) {
ExistingGenerator generator = new ExistingGenerator();
for (Object value : values)
generator.addObject(value);
return generator;
}
private void addObject(Object value) {
if (value == null)
throw new IllegalArgumentException("Value cannot be NULL.");
existingValues.put(value.getClass().getName(), value);
}
private void addObject(Class<?> type, Object value) {
existingValues.put(type.getName(), value);
}
@Override
public Object create(@Nullable Class<?> type) {
Object value = existingValues.get(type.getName());
// NULL values indicate that the generator failed
return value;
}
}
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff. Show more