diff --git a/ProtocolLib/.project b/ProtocolLib/.project index 3b5df9da..a1960c9e 100644 --- a/ProtocolLib/.project +++ b/ProtocolLib/.project @@ -15,9 +15,15 @@ + + net.sourceforge.metrics.builder + + + org.eclipse.m2e.core.maven2Nature org.eclipse.jdt.core.javanature + net.sourceforge.metrics.nature diff --git a/ProtocolLib/dependency-reduced-pom.xml b/ProtocolLib/dependency-reduced-pom.xml index 00d4c784..ea37303c 100644 --- a/ProtocolLib/dependency-reduced-pom.xml +++ b/ProtocolLib/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.comphenix.protocol ProtocolLib ProtocolLib - 1.8.0 + 1.9.0 Provides read/write access to the Minecraft protocol. http://dev.bukkit.org/server-mods/protocollib/ @@ -139,7 +139,7 @@ org.bukkit craftbukkit - 1.4.5-R0.3-SNAPSHOT + 1.4.6-R0.1 provided @@ -154,6 +154,36 @@ + + org.mockito + mockito-all + 1.8.4 + test + + + org.powermock + powermock-module-junit4 + 1.5 + test + + + powermock-module-junit4-common + org.powermock + + + + + org.powermock + powermock-api-mockito + 1.5 + test + + + powermock-api-support + org.powermock + + + @@ -181,6 +211,7 @@ + 1.5 cp1252 diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 421b5e48..c95ebec1 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,12 +2,13 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 1.8.0 + 1.9.0 jar Provides read/write access to the Minecraft protocol. cp1252 + 1.5 @@ -202,7 +203,7 @@ org.bukkit craftbukkit - 1.4.5-R0.3-SNAPSHOT + 1.4.6-R0.1 provided @@ -211,5 +212,23 @@ 4.10 test + + org.mockito + mockito-all + 1.8.4 + test + + + org.powermock + powermock-module-junit4 + ${powermock.version} + test + + + org.powermock + powermock-api-mockito + ${powermock.version} + test + \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java index f04aebdc..f4940bee 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol; import java.lang.reflect.Field; @@ -13,7 +30,7 @@ import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.MethodUtils; -import com.comphenix.protocol.reflect.ObjectCloner; +import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; import com.comphenix.protocol.reflect.compiler.StructureCompiler; import com.comphenix.protocol.reflect.instances.CollectionGenerator; @@ -48,7 +65,7 @@ class CleanupStaticMembers { BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class, PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class, BackgroundCompiler.class, StructureCompiler.class, - ObjectCloner.class, Packets.Server.class, Packets.Client.class, + ObjectWriter.class, Packets.Server.class, Packets.Client.class, ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class }; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandBase.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandBase.java index 7cda5346..18ddb25e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandBase.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandBase.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol; import org.bukkit.ChatColor; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index 5719a097..4da7a3c4 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol; import java.lang.reflect.InvocationTargetException; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java index d4c6aa05..c35f7632 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol; import org.bukkit.ChatColor; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/MinecraftVersion.java b/ProtocolLib/src/main/java/com/comphenix/protocol/MinecraftVersion.java index 6544ac11..53fb9898 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/MinecraftVersion.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/MinecraftVersion.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol; import java.util.regex.Matcher; @@ -7,6 +24,7 @@ import org.bukkit.Server; import com.google.common.base.Objects; import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Ordering; /** * Determine the current Minecraft version. @@ -22,6 +40,9 @@ class MinecraftVersion implements Comparable { private final int major; private final int minor; private final int build; + + // The development stage + private final String development; /** * Determine the current Minecraft version. @@ -36,11 +57,13 @@ class MinecraftVersion implements Comparable { * @param versionOnly - the version in text form. */ public MinecraftVersion(String versionOnly) { - int[] numbers = parseVersion(versionOnly); + String[] section = versionOnly.split("-"); + int[] numbers = parseVersion(section[0]); this.major = numbers[0]; this.minor = numbers[1]; this.build = numbers[2]; + this.development = section.length > 1 ? section[1] : null; } /** @@ -50,9 +73,21 @@ class MinecraftVersion implements Comparable { * @param build - build version number. */ public MinecraftVersion(int major, int minor, int build) { + this(major, minor, build, null); + } + + /** + * Construct a version object directly. + * @param major - major version number. + * @param minor - minor version number. + * @param build - build version number. + * @param development - development stage. + */ + public MinecraftVersion(int major, int minor, int build, String development) { this.major = major; this.minor = minor; this.build = build; + this.development = development; } private int[] parseVersion(String version) { @@ -65,7 +100,7 @@ class MinecraftVersion implements Comparable { // The String 1 or 1.2 is interpreted as 1.0.0 and 1.2.0 respectively. for (int i = 0; i < Math.min(numbers.length, elements.length); i++) - numbers[i] = Integer.parseInt(elements[i].trim()); + numbers[i] = Integer.parseInt(elements[i].trim()); return numbers; } @@ -93,12 +128,23 @@ class MinecraftVersion implements Comparable { return build; } + /** + * Retrieve the development stage. + * @return Development stage, or NULL if this is a release. + */ + public String getDevelopmentStage() { + return development; + } + /** * Retrieve the version String (major.minor.build) only. * @return A normal version string. */ public String getVersion() { - return String.format("%s.%s.%s", major, minor, build); + if (development == null) + return String.format("%s.%s.%s", major, minor, build); + else + return String.format("%s.%s.%s-%s", major, minor, build, development); } @Override @@ -110,6 +156,8 @@ class MinecraftVersion implements Comparable { compare(major, o.major). compare(minor, o.minor). compare(build, o.build). + // No development String means it's a release + compare(development, o.development, Ordering.natural().nullsLast()). result(); } @@ -125,7 +173,8 @@ class MinecraftVersion implements Comparable { return major == other.major && minor == other.minor && - build == other.build; + build == other.build && + Objects.equal(development, other.development); } return false; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java index f135fa97..040a8ef1 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol; import java.io.File; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 2f5b4774..c8c3e0a8 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -17,11 +17,14 @@ package com.comphenix.protocol; +import java.io.File; import java.io.IOException; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.bukkit.Server; import org.bukkit.command.CommandExecutor; @@ -52,7 +55,7 @@ public class ProtocolLibrary extends JavaPlugin { /** * The maximum version ProtocolLib has been tested with, */ - private static final String MAXIMUM_MINECRAFT_VERSION = "1.4.5"; + private static final String MAXIMUM_MINECRAFT_VERSION = "1.4.6"; /** * The number of milliseconds per second. @@ -65,7 +68,7 @@ public class ProtocolLibrary extends JavaPlugin { private static PacketFilterManager protocolManager; // Error reporter - private ErrorReporter reporter; + private static ErrorReporter reporter; // Metrics and statistisc private Statistics statistisc; @@ -97,6 +100,9 @@ public class ProtocolLibrary extends JavaPlugin { private CommandProtocol commandProtocol; private CommandPacket commandPacket; + // Whether or not disable is not needed + private boolean skipDisable; + @Override public void onLoad() { // Load configuration @@ -104,7 +110,6 @@ public class ProtocolLibrary extends JavaPlugin { // Add global parameters DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this); - updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info"); reporter = detailedReporter; try { @@ -121,6 +126,12 @@ public class ProtocolLibrary extends JavaPlugin { } try { + // Check for other versions + checkConflictingVersions(); + + // Set updater + updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info"); + unhookTask = new DelayedSingleTask(this); protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, detailedReporter); detailedReporter.addGlobalParameter("manager", protocolManager); @@ -252,6 +263,45 @@ public class ProtocolLibrary extends JavaPlugin { reporter.reportWarning(this, "Unable to retrieve current Minecraft version.", e); } } + + private void checkConflictingVersions() { + Pattern ourPlugin = Pattern.compile("ProtocolLib-(.*)\\.jar"); + MinecraftVersion currentVersion = new MinecraftVersion(this.getDescription().getVersion()); + MinecraftVersion newestVersion = null; + + try { + // Scan the plugin folder for newer versions of ProtocolLib + File pluginFolder = new File("plugins/"); + + for (File candidate : pluginFolder.listFiles()) { + if (candidate.isFile()) { + Matcher match = ourPlugin.matcher(candidate.getName()); + + if (match.matches()) { + MinecraftVersion version = new MinecraftVersion(match.group(1)); + + if (newestVersion == null || newestVersion.compareTo(version) < 0) { + newestVersion = version; + } + } + } + } + + } catch (Exception e) { + reporter.reportWarning(this, "Unable to detect conflicting plugin versions.", e); + } + + // See if the newest version is actually higher + if (newestVersion != null && currentVersion.compareTo(newestVersion) < 0) { + // We don't need to set internal classes or instances to NULL - that would break the other loaded plugin + skipDisable = true; + + throw new IllegalStateException( + String.format("Detected a newer version of ProtocolLib (%s) in plugin folder than the current (%s). Disabling.", + newestVersion.getVersion(), currentVersion.getVersion()) + ); + } + } private void registerCommand(String name, CommandExecutor executor) { try { @@ -331,6 +381,10 @@ public class ProtocolLibrary extends JavaPlugin { @Override public void onDisable() { + if (skipDisable) { + return; + } + // Disable compiler if (backgroundCompiler != null) { backgroundCompiler.shutdownAll(); @@ -353,6 +407,7 @@ public class ProtocolLibrary extends JavaPlugin { protocolManager.close(); protocolManager = null; statistisc = null; + reporter = null; // Leaky ClassLoader begone! CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), reporter); @@ -373,6 +428,14 @@ public class ProtocolLibrary extends JavaPlugin { return log; } + /** + * Retrieve the current error reporter. + * @return Current error reporter. + */ + public static ErrorReporter getErrorReporter() { + return reporter; + } + /** * Retrieves the packet protocol manager. * @return Packet protocol manager, or NULL if it has been disabled. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/RangeParser.java b/ProtocolLib/src/main/java/com/comphenix/protocol/RangeParser.java index d396be43..f893f731 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/RangeParser.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/RangeParser.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol; import java.util.ArrayList; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/PlayerSendingHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/PlayerSendingHandler.java index 8dc4d0be..e2aa0f09 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/PlayerSendingHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/PlayerSendingHandler.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.async; import java.util.ArrayList; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java index 9b241e90..7d29ddf2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.concurrency; import java.util.HashSet; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java index 793f3c11..df867951 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.concurrency; import java.util.Collection; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java index 9b6ef329..09786d1a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.error; import java.io.PrintWriter; @@ -6,6 +23,9 @@ import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; @@ -15,6 +35,7 @@ import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.reflect.PrettyPrinter; import com.google.common.primitives.Primitives; /** @@ -35,10 +56,14 @@ public class DetailedErrorReporter implements ErrorReporter { // We don't want to spam the server public static final int DEFAULT_MAX_ERROR_COUNT = 20; + // Prevent spam per plugin too + private ConcurrentMap warningCount = new ConcurrentHashMap(); + protected String prefix; protected String supportURL; - protected int errorCount; + protected AtomicInteger internalErrorCount = new AtomicInteger(); + protected int maxErrorCount; protected Logger logger; @@ -97,23 +122,65 @@ public class DetailedErrorReporter implements ErrorReporter { @Override public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { - reportMinimal(sender, methodName, error); - - // Print parameters, if they are given - if (parameters != null && parameters.length > 0) { - logger.log(Level.SEVERE, " Parameters:"); - - // Print each parameter - for (Object parameter : parameters) { - logger.log(Level.SEVERE, " " + getStringDescription(parameter)); + if (reportMinimalNoSpam(sender, methodName, error)) { + // Print parameters, if they are given + if (parameters != null && parameters.length > 0) { + logger.log(Level.SEVERE, " Parameters:"); + + // Print each parameter + for (Object parameter : parameters) { + logger.log(Level.SEVERE, " " + getStringDescription(parameter)); + } } } } @Override public void reportMinimal(Plugin sender, String methodName, Throwable error) { - logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception occured in " + methodName + " for " + - PacketAdapter.getPluginName(sender), error); + reportMinimalNoSpam(sender, methodName, error); + } + + public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) { + String pluginName = PacketAdapter.getPluginName(sender); + AtomicInteger counter = warningCount.get(pluginName); + + // Thread safe pattern + if (counter == null) { + AtomicInteger created = new AtomicInteger(); + counter = warningCount.putIfAbsent(pluginName, created); + + if (counter == null) { + counter = created; + } + } + + final int errorCount = counter.incrementAndGet(); + + // See if we should print the full error + if (errorCount < getMaxErrorCount()) { + logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception occured in " + + methodName + " for " + pluginName, error); + return true; + + } else { + // Nope - only print the error count occationally + if (isPowerOfTwo(errorCount)) { + logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception number " + errorCount + " occured in " + + methodName + " for " + pluginName, error); + } + return false; + } + } + + /** + * Determine if a given number is a power of two. + *

+ * That is, if there exists an N such that 2^N = number. + * @param number - the number to check. + * @return TRUE if the given number is a power of two, FALSE otherwise. + */ + private boolean isPowerOfTwo(int number) { + return (number & (number - 1)) == 0; } @Override @@ -137,12 +204,18 @@ public class DetailedErrorReporter implements ErrorReporter { public void reportDetailed(Object sender, String message, Throwable error, Object... parameters) { final Plugin plugin = pluginReference.get(); + final int errorCount = internalErrorCount.incrementAndGet(); // Do not overtly spam the server! - if (++errorCount > maxErrorCount) { - String maxReached = String.format("Reached maxmimum error count. Cannot pass error %s from %s.", error, sender); - logger.severe(maxReached); - return; + if (errorCount > getMaxErrorCount()) { + // Only allow the error count at rare occations + if (isPowerOfTwo(errorCount)) { + // Permit it - but print the number of exceptions first + reportWarning(this, "Internal exception count: " + errorCount + "!"); + } else { + // NEVER SPAM THE CONSOLE + return; + } } StringWriter text = new StringWriter(); @@ -230,12 +303,15 @@ public class DetailedErrorReporter implements ErrorReporter { return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null)); } catch (Throwable ex) { // Apache is probably missing - logger.warning("Cannot find Apache Commons. Object introspection disabled."); apacheCommonsMissing = true; } - // Just use toString() - return String.format("%s", value); + // Use our custom object printer instead + try { + return PrettyPrinter.printObject(value, value.getClass(), Object.class); + } catch (IllegalAccessException e) { + return "[Error: " + e.getMessage() + "]"; + } } } @@ -249,11 +325,11 @@ public class DetailedErrorReporter implements ErrorReporter { } public int getErrorCount() { - return errorCount; + return internalErrorCount.get(); } public void setErrorCount(int errorCount) { - this.errorCount = errorCount; + internalErrorCount.set(errorCount); } public int getMaxErrorCount() { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/ErrorReporter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ErrorReporter.java index 54b45f48..e1b7f927 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/ErrorReporter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ErrorReporter.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.error; import org.bukkit.plugin.Plugin; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/MonitorAdapter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/MonitorAdapter.java index 832c2d8f..358c6bd0 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/MonitorAdapter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/MonitorAdapter.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.events; import java.util.logging.Level; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index a7b4a265..c724a266 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -17,20 +17,21 @@ package com.comphenix.protocol.events; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentMap; +import javax.annotation.Nullable; + import org.bukkit.World; import org.bukkit.WorldType; import org.bukkit.entity.Entity; @@ -39,12 +40,22 @@ import org.bukkit.inventory.ItemStack; import com.comphenix.protocol.injector.StructureCache; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.cloning.AggregateCloner; +import com.comphenix.protocol.reflect.cloning.BukkitCloner; +import com.comphenix.protocol.reflect.cloning.Cloner; +import com.comphenix.protocol.reflect.cloning.CollectionCloner; +import com.comphenix.protocol.reflect.cloning.FieldCloner; +import com.comphenix.protocol.reflect.cloning.ImmutableDetector; +import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters; +import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.wrappers.BukkitConverters; import com.comphenix.protocol.wrappers.ChunkPosition; import com.comphenix.protocol.wrappers.WrappedDataWatcher; import com.comphenix.protocol.wrappers.WrappedWatchableObject; +import com.google.common.base.Function; import com.google.common.collect.Maps; /** @@ -69,6 +80,28 @@ public class PacketContainer implements Serializable { private static ConcurrentMap, Method> writeMethods = Maps.newConcurrentMap(); private static ConcurrentMap, Method> readMethods = Maps.newConcurrentMap(); + // Used to clone packets + private static final AggregateCloner DEEP_CLONER = AggregateCloner.newBuilder(). + instanceProvider(DefaultInstances.DEFAULT). + andThen(BukkitCloner.class). + andThen(ImmutableDetector.class). + andThen(CollectionCloner.class). + andThen(getSpecializedDeepClonerFactory()). + build(); + + private static final AggregateCloner SHALLOW_CLONER = AggregateCloner.newBuilder(). + instanceProvider(DefaultInstances.DEFAULT). + andThen(new Function() { + @Override + public Cloner apply(@Nullable BuilderParameters param) { + return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{ + // Use a default writer with no concept of cloning + writer = new ObjectWriter(); + }}; + } + }). + build(); + /** * Creates a packet container for a new packet. * @param id - ID of the packet to create. @@ -241,12 +274,12 @@ public class PacketContainer implements Serializable { BukkitConverters.getIgnoreNull(new EquivalentConverter() { public Object getGeneric(ClassgenericType, ItemStack[] specific) { - Object[] result = new Object[specific.length]; + Class nmsStack = MinecraftReflection.getItemStackClass(); + Object[] result = (Object[]) Array.newInstance(nmsStack, specific.length); // Unwrap every item for (int i = 0; i < result.length; i++) { - result[i] = stackConverter.getGeneric( - MinecraftReflection.getItemStackClass(), specific[i]); + result[i] = stackConverter.getGeneric(nmsStack, specific[i]); } return result; } @@ -364,40 +397,54 @@ public class PacketContainer implements Serializable { return id; } + /** + * Create a shallow copy of the current packet. + *

+ * This merely writes the content of each field to the new class directly, + * without performing any expensive copies. + * + * @return A shallow copy of the current packet. + */ + public PacketContainer shallowClone() { + Object clonedPacket = SHALLOW_CLONER.clone(getHandle()); + return new PacketContainer(getID(), clonedPacket); + } + /** * Create a deep copy of the current packet. + *

+ * This will perform a full copy of the entire object tree, only skipping + * known immutable objects and primitive types. + *

+ * Note that the inflated buffers in packet 51 and 56 will be copied directly to save memory. + * * @return A deep copy of the current packet. */ public PacketContainer deepClone() { - ObjectOutputStream output = null; - ObjectInputStream input = null; - - try { - // Use a small buffer of 32 bytes initially. - ByteArrayOutputStream bufferOut = new ByteArrayOutputStream(); - output = new ObjectOutputStream(bufferOut); - output.writeObject(this); - - ByteArrayInputStream bufferIn = new ByteArrayInputStream(bufferOut.toByteArray()); - input = new ObjectInputStream(bufferIn); - return (PacketContainer) input.readObject(); - - } catch (IOException e) { - throw new IllegalStateException("Unexpected error occured during object cloning.", e); - } catch (ClassNotFoundException e) { - // Cannot happen - throw new IllegalStateException("Unexpected failure with serialization.", e); - } finally { - try { - if (output != null) - output.close(); - if (input != null) - input.close(); - - } catch (IOException e) { - // STOP IT + Object clonedPacket = DEEP_CLONER.clone(getHandle()); + return new PacketContainer(getID(), clonedPacket); + } + + // To save space, we'll skip copying the inflated buffers in packet 51 and 56 + private static Function getSpecializedDeepClonerFactory() { + // Look at what you've made me do Java, look at it!! + return new Function() { + @Override + public Cloner apply(@Nullable BuilderParameters param) { + return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{ + this.writer = new ObjectWriter() { + protected void transformField(StructureModifier modifierSource, + StructureModifier modifierDest, int fieldIndex) { + // No need to clone inflated buffers + if (modifierSource.getField(fieldIndex).getName().startsWith("inflatedBuffer")) + modifierDest.write(fieldIndex, modifierSource.read(fieldIndex)); + else + defaultTransform(modifierSource, modifierDest, getDefaultCloner(), fieldIndex); + }; + }; + }}; } - } + }; } private void writeObject(ObjectOutputStream output) throws IOException { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java index 147e2087..ae444147 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java @@ -17,14 +17,18 @@ package com.comphenix.protocol.injector; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.injector.PacketConstructor.Unwrapper; +import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.instances.DefaultInstances; +import com.google.common.primitives.Primitives; /** * Represents an object capable of converting wrapped Bukkit objects into NMS objects. @@ -38,41 +42,33 @@ import com.comphenix.protocol.reflect.instances.DefaultInstances; * @author Kristian */ public class BukkitUnwrapper implements Unwrapper { + private static Map, Unwrapper> unwrapperCache = new ConcurrentHashMap, Unwrapper>(); - private static Map, Method> cache = new ConcurrentHashMap, Method>(); - @SuppressWarnings("unchecked") @Override public Object unwrapItem(Object wrappedObject) { - - // Special cases - if (wrappedObject == null) { + // Special case + if (wrappedObject == null) return null; - } else if (wrappedObject instanceof Collection) { - return handleCollection((Collection) 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 + // Next, check for types that doesn't have a getHandle() + if (wrappedObject instanceof Collection) { + return handleCollection((Collection) wrappedObject); + } else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) { return null; - } catch (IllegalAccessException e) { - return null; - } catch (InvocationTargetException e) { - // This is REALLY bad - throw new RuntimeException("Minecraft error.", e); } + + Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass); + + // Retrieve the handle + if (specificUnwrapper != null) + return specificUnwrapper.unwrapItem(wrappedObject); + else + return null; } + // Handle a collection of items private Object handleCollection(Collection wrappedObject) { @SuppressWarnings("unchecked") @@ -91,24 +87,94 @@ public class BukkitUnwrapper implements Unwrapper { } } - private Method initializeCache(Class type) { - + /** + * Retrieve a cached class unwrapper for the given class. + * @param type - the type of the class. + * @return An unwrapper for the given class. + */ + private Unwrapper getSpecificUnwrapper(Class type) { // See if we're already determined this - if (cache.containsKey(type)) { + if (unwrapperCache.containsKey(type)) { // We will never remove from the cache, so this ought to be thread safe - return cache.get(type); + return unwrapperCache.get(type); } try { - Method find = type.getMethod("getHandle"); + final Method find = type.getMethod("getHandle"); // It's thread safe, as getMethod should return the same handle - cache.put(type, find); - return find; + Unwrapper methodUnwrapper = new Unwrapper() { + @Override + public Object unwrapItem(Object wrappedObject) { + + try { + return find.invoke(wrappedObject); + + } catch (IllegalArgumentException e) { + ProtocolLibrary.getErrorReporter().reportDetailed( + this, "Illegal argument.", e, wrappedObject, find); + } catch (IllegalAccessException e) { + // Should not occur either + return null; + } catch (InvocationTargetException e) { + // This is really bad + throw new RuntimeException("Minecraft error.", e); + } + + return null; + } + }; + + unwrapperCache.put(type, methodUnwrapper); + return methodUnwrapper; } catch (SecurityException e) { - return null; + ProtocolLibrary.getErrorReporter().reportDetailed(this, "Security limitation.", e, type.getName()); } catch (NoSuchMethodException e) { + // Try getting the field unwrapper too + Unwrapper fieldUnwrapper = getFieldUnwrapper(type); + + if (fieldUnwrapper != null) + return fieldUnwrapper; + else + ProtocolLibrary.getErrorReporter().reportDetailed(this, "Cannot find method.", e, type.getName()); + } + + // Default method + return null; + } + + /** + * Retrieve a cached unwrapper using the handle field. + * @param type - a cached field unwrapper. + * @return The cached field unwrapper. + */ + private Unwrapper getFieldUnwrapper(Class type) { + final Field find = FieldUtils.getField(type, "handle", true); + + // See if we succeeded + if (find != null) { + Unwrapper fieldUnwrapper = new Unwrapper() { + @Override + public Object unwrapItem(Object wrappedObject) { + try { + return FieldUtils.readField(find, wrappedObject, true); + } catch (IllegalAccessException e) { + ProtocolLibrary.getErrorReporter().reportDetailed( + this, "Cannot read field 'handle'.", e, wrappedObject, find.getName()); + return null; + } + } + }; + + unwrapperCache.put(type, fieldUnwrapper); + return fieldUnwrapper; + + } else { + // Inform about this too + ProtocolLibrary.getErrorReporter().reportDetailed( + this, "Could not find field 'handle'.", + new Exception("Unable to find 'handle'"), type.getName()); return null; } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedSingleTask.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedSingleTask.java index b44de2ee..41242067 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedSingleTask.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedSingleTask.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.injector; import org.bukkit.plugin.Plugin; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/GamePhase.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/GamePhase.java index 07c7e7a5..399b3350 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/GamePhase.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/GamePhase.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.injector; /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/MinecraftRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/MinecraftRegistry.java index 5cdddd7c..7ad014cb 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/MinecraftRegistry.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/MinecraftRegistry.java @@ -182,6 +182,22 @@ class MinecraftRegistry { throw new IllegalArgumentException("The packet ID " + packetID + " is not registered."); } + /** + * Retrieve the packet ID of a given packet. + * @param packet - the type of packet to check. + * @return The ID of the given packet. + * @throws IllegalArgumentException If this is not a valid packet. + */ + public static int getPacketID(Class packet) { + if (packet == null) + throw new IllegalArgumentException("Packet type class cannot be NULL."); + if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet)) + throw new IllegalArgumentException("Type must be a packet."); + + // The registry contains both the overridden and original packets + return getPacketToID().get(packet); + } + /** * Find the first superclass that is not a CBLib proxy object. * @param clazz - the class whose hierachy we're going to search through. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/StructureCache.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/StructureCache.java index bc4f7aa2..b76f2179 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/StructureCache.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/StructureCache.java @@ -64,6 +64,27 @@ public class StructureCache { return getStructure(id, true); } + /** + * Retrieve a cached structure modifier given a packet type. + * @param packetType - packet type. + * @return A structure modifier. + */ + public static StructureModifier getStructure(Class packetType) { + // Compile structures by default + return getStructure(packetType, true); + } + + /** + * Retrieve a cached structure modifier given a packet type. + * @param packetType - packet type. + * @param compile - whether or not to asynchronously compile the structure modifier. + * @return A structure modifier. + */ + public static StructureModifier getStructure(Class packetType, boolean compile) { + // Get the ID from the class + return getStructure(MinecraftRegistry.getPacketID(packetType), compile); + } + /** * Retrieve a cached structure modifier for the given packet id. * @param id - packet ID. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java index 1d94a9c9..6123ec0f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java @@ -29,7 +29,7 @@ import org.bukkit.Server; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.ObjectCloner; +import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.utility.MinecraftReflection; @@ -77,7 +77,8 @@ class InjectedServerConnection { return; if (minecraftServerField == null) - minecraftServerField = FuzzyReflection.fromObject(server, true).getFieldByType(".*MinecraftServer"); + minecraftServerField = FuzzyReflection.fromObject(server, true). + getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass()); try { minecraftServer = FieldUtils.readField(minecraftServerField, server, true); @@ -211,6 +212,9 @@ class InjectedServerConnection { * Shut up Eclipse! */ private static final long serialVersionUID = 2070481080950500367L; + + // Object writer we'll use + private final ObjectWriter writer = new ObjectWriter(); @Override protected void onReplacing(Object inserting, Object replacement) { @@ -218,7 +222,7 @@ class InjectedServerConnection { if (!(inserting instanceof Factory)) { // If so, copy the content of the old element to the new try { - ObjectCloner.copyTo(inserting, replacement, inserting.getClass()); + writer.copyTo(inserting, replacement, inserting.getClass()); } catch (Throwable e) { reporter.reportDetailed(InjectedServerConnection.this, "Cannot copy old " + inserting + " to new.", e, inserting, replacement); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/IntegerSet.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/IntegerSet.java index b877bc2a..3deea142 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/IntegerSet.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/IntegerSet.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.injector.player; import java.util.Arrays; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index 64e2f005..dd03ea5d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.injector.player; import java.util.concurrent.ConcurrentMap; @@ -8,6 +25,7 @@ import org.bukkit.entity.Player; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer; +import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.collect.Maps; /** @@ -62,9 +80,9 @@ class NetLoginInjector { } catch (Throwable e) { // Minecraft can't handle this, so we'll deal with it here - reporter.reportDetailed(this, "Unable to hook NetLoginHandler.", e, inserting); + reporter.reportDetailed(this, "Unable to hook " + + MinecraftReflection.getNetLoginHandlerName() + ".", e, inserting); return inserting; - } } @@ -103,7 +121,8 @@ class NetLoginInjector { } catch (Throwable e) { // Don't leak this to Minecraft - reporter.reportDetailed(this, "Cannot cleanup NetLoginHandler.", e, removing); + reporter.reportDetailed(this, "Cannot cleanup " + + MinecraftReflection.getNetLoginHandlerName() + ".", e, removing); } } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java index a7b42de2..74a0a3e6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java @@ -38,7 +38,7 @@ import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.ObjectCloner; +import com.comphenix.protocol.reflect.ObjectWriter; import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.ExistingGenerator; @@ -66,6 +66,9 @@ public class NetworkServerInjector extends PlayerInjector { // Whether or not the player has disconnected private boolean hasDisconnected; + // Used to copy fields + private final ObjectWriter writer = new ObjectWriter(); + public NetworkServerInjector( ClassLoader classLoader, ErrorReporter reporter, Player player, ListenerInvoker invoker, IntegerSet sendingFilters, @@ -141,7 +144,8 @@ public class NetworkServerInjector extends PlayerInjector { } throw new RuntimeException( - "Cannot hook player: Unable to find a valid constructor for the NetServerHandler object."); + "Cannot hook player: Unable to find a valid constructor for the " + + MinecraftReflection.getNetServerHandlerClass().getName() + " object."); } } @@ -247,7 +251,7 @@ public class NetworkServerInjector extends PlayerInjector { @Override protected void cleanHook() { if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) { - ObjectCloner.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass()); + writer.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass()); serverHandlerRef.revertValue(); try { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java index 239f6b7a..25d683c6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java @@ -156,7 +156,8 @@ abstract class PlayerInjector { // Retrieve the server handler if (serverHandlerField == null) { - serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler"); + serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType( + "NetServerHandler", MinecraftReflection.getNetServerHandlerClass()); proxyServerField = getProxyField(notchEntity, serverHandlerField); } @@ -166,7 +167,8 @@ abstract class PlayerInjector { // Next, get the network manager if (networkManagerField == null) - networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(".*NetworkManager"); + networkManagerField = FuzzyReflection.fromObject(serverHandler). + getFieldByType(".*" + MinecraftReflection.getNetworkManagerName()); initializeNetworkManager(networkManagerField, serverHandler); } } @@ -181,7 +183,8 @@ abstract class PlayerInjector { loginHandler = netLoginHandler; if (netLoginNetworkField == null) - netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler).getFieldByType(".*NetworkManager"); + netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler). + getFieldByType(".*" + MinecraftReflection.getNetworkManagerName()); initializeNetworkManager(netLoginNetworkField, netLoginHandler); } } @@ -341,7 +344,7 @@ abstract class PlayerInjector { FuzzyReflection reflection = FuzzyReflection.fromObject(handler, true); // It might be - return reflection.getFieldByType(".*NetServerHandler"); + return reflection.getFieldByType("NetServerHandler", MinecraftReflection.getNetServerHandlerClass()); } catch (RuntimeException e) { // Damn @@ -367,7 +370,7 @@ abstract class PlayerInjector { try { if (netHandlerField == null) netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true). - getFieldByType("net\\.minecraft\\.NetHandler"); + getFieldByType("NetHandler", MinecraftReflection.getNetHandlerClass()); } catch (RuntimeException e1) { // Swallow it } @@ -398,7 +401,8 @@ abstract class PlayerInjector { */ private Object getEntityPlayer(Object netHandler) throws IllegalAccessException { if (entityPlayerField == null) - entityPlayerField = FuzzyReflection.fromObject(netHandler).getFieldByType(".*EntityPlayer"); + entityPlayerField = FuzzyReflection.fromObject(netHandler).getFieldByType( + "EntityPlayer", MinecraftReflection.getEntityPlayerClass()); return FieldUtils.readField(entityPlayerField, netHandler); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java index 891edc7c..45942257 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.injector.player; import java.lang.reflect.InvocationTargetException; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/UnsupportedListener.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/UnsupportedListener.java index 2e4cbe5a..121428d8 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/UnsupportedListener.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/UnsupportedListener.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.injector.player; import java.util.Arrays; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectCloner.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectWriter.java similarity index 50% rename from ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectCloner.java rename to ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectWriter.java index 5475e60d..5ab3eb2d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectCloner.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectWriter.java @@ -17,47 +17,88 @@ package com.comphenix.protocol.reflect; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import com.comphenix.protocol.injector.StructureCache; +import com.comphenix.protocol.utility.MinecraftReflection; /** * Can copy an object field by field. * * @author Kristian */ -public class ObjectCloner { - +public class ObjectWriter { // Cache structure modifiers @SuppressWarnings("rawtypes") private static ConcurrentMap> cache = new ConcurrentHashMap>(); /** - * Copy every field in object A to object B. + * Retrieve a usable structure modifier for the given object type. + *

+ * Will attempt to reuse any other structure modifiers we have cached. + * @param type - the type of the object we are modifying. + * @return A structure modifier for the given type. + */ + private StructureModifier getModifier(Class type) { + Class packetClass = MinecraftReflection.getPacketClass(); + + // Handle subclasses of the packet class with our custom structure cache + if (!type.equals(packetClass) && packetClass.isAssignableFrom(type)) { + // Delegate to our already existing registry of structure modifiers + return StructureCache.getStructure(type); + } + + StructureModifier modifier = cache.get(type); + + // Create the structure modifier if we haven't already + if (modifier == null) { + StructureModifier value = new StructureModifier(type, null, false); + modifier = cache.putIfAbsent(type, value); + + if (modifier == null) + modifier = value; + } + + // And we're done + return modifier; + } + + /** + * Copy every field in object A to object B. Each value is copied directly, and is not cloned. *

* The two objects must have the same number of fields of the same type. * @param source - fields to copy. * @param destination - fields to copy to. * @param commonType - type containing each field to copy. */ - public static void copyTo(Object source, Object destination, Class commonType) { - + public void copyTo(Object source, Object destination, Class commonType) { + // Note that we indicate that public fields will be copied the first time around + copyToInternal(source, destination, commonType, true); + } + + /** + * Called for every non-static field that will be copied. + * @param modifierSource - modifier for the original object. + * @param modifierDest - modifier for the new cloned object. + * @param fieldIndex - the current field index. + */ + protected void transformField(StructureModifier modifierSource, StructureModifier modifierDest, int fieldIndex) { + Object value = modifierSource.read(fieldIndex); + modifierDest.write(fieldIndex, value); + } + + // Internal method that will actually implement the recursion + private void copyToInternal(Object source, Object destination, Class commonType, boolean copyPublic) { if (source == null) throw new IllegalArgumentException("Source cannot be NULL"); if (destination == null) throw new IllegalArgumentException("Destination cannot be NULL"); - StructureModifier modifier = cache.get(commonType); - - // Create the structure modifier if we haven't already - if (modifier == null) { - StructureModifier value = new StructureModifier(commonType, null, false); - modifier = cache.putIfAbsent(commonType, value); - - if (modifier == null) - modifier = value; - } + StructureModifier modifier = getModifier(commonType); // Add target StructureModifier modifierSource = modifier.withTarget(source); @@ -66,20 +107,20 @@ public class ObjectCloner { // Copy every field try { for (int i = 0; i < modifierSource.size(); i++) { - if (!modifierDest.isReadOnly(i)) { - Object value = modifierSource.read(i); - modifierDest.write(i, value); - } + Field field = modifierSource.getField(i); + int mod = field.getModifiers(); - // System.out.println(String.format("Writing value %s to %s", - // value, modifier.getFields().get(i).getName())); + // Skip static fields. We also get the "public" fields fairly often, so we'll skip that. + if (!Modifier.isStatic(mod) && (!Modifier.isPublic(mod) || copyPublic)) { + transformField(modifierSource, modifierDest, i); + } } // Copy private fields underneath Class superclass = commonType.getSuperclass(); if (!superclass.equals(Object.class)) { - copyTo(source, destination, superclass); + copyToInternal(source, destination, superclass, false); } } catch (FieldAccessException e) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java index 33ceb604..ad285df8 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.reflect; import java.lang.reflect.Array; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java index 87a309fa..9d90d0b8 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java @@ -201,10 +201,16 @@ public class StructureModifier { * @return TRUE if the field by the given index is read-only, FALSE otherwise. */ public boolean isReadOnly(int fieldIndex) { - if (fieldIndex < 0 || fieldIndex >= data.size()) - throw new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")"); - - return Modifier.isFinal(data.get(fieldIndex).getModifiers()); + return Modifier.isFinal(getField(fieldIndex).getModifiers()); + } + + /** + * Determine if a given field is public or not. + * @param fieldIndex - field index. + * @return TRUE if the field is public, FALSE otherwise. + */ + public boolean isPublic(int fieldIndex) { + return Modifier.isPublic(getField(fieldIndex).getModifiers()); } /** @@ -499,6 +505,19 @@ public class StructureModifier { return ImmutableList.copyOf(data); } + /** + * Retrieve a field by index. + * @param fieldIndex - index of the field to retrieve. + * @return The field represented with the given index. + * @throws IllegalArgumentException If no field with the given index can be found. + */ + public Field getField(int fieldIndex) { + if (fieldIndex < 0 || fieldIndex >= data.size()) + throw new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")"); + + return data.get(fieldIndex); + } + /** * Retrieve every value stored in the fields of the current type. * @return Every field value. @@ -560,4 +579,6 @@ public class StructureModifier { return result; } + + } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/AggregateCloner.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/AggregateCloner.java new file mode 100644 index 00000000..19874b5a --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/AggregateCloner.java @@ -0,0 +1,263 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.reflect.cloning; + +import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.List; + +import javax.annotation.Nullable; + +import com.comphenix.protocol.reflect.instances.DefaultInstances; +import com.comphenix.protocol.reflect.instances.ExistingGenerator; +import com.comphenix.protocol.reflect.instances.InstanceProvider; +import com.google.common.base.Function; +import com.google.common.collect.Lists; + +/** + * Implements a cloning procedure by trying multiple methods in turn until one is successful. + * + * @author Kristian + */ +public class AggregateCloner implements Cloner { + /** + * Supplies the cloner factories with necessary parameters. + * + * @author Kristian + */ + public static class BuilderParameters { + // Can only be modified by the builder + private InstanceProvider instanceProvider; + private Cloner aggregateCloner; + + // Used to construct the different types + private InstanceProvider typeConstructor; + + private BuilderParameters() { + // Only allow inner classes to construct it. + } + + /** + * Retrieve the instance provider last set in the builder. + * @return Current instance provider. + */ + public InstanceProvider getInstanceProvider() { + return instanceProvider; + } + + /** + * Retrieve the aggregate cloner that is being built. + * @return The parent cloner. + */ + public Cloner getAggregateCloner() { + return aggregateCloner; + } + } + + /** + * Represents a builder for aggregate (combined) cloners. + * + * @author Kristian + */ + public static class Builder { + private List> factories = Lists.newArrayList(); + private BuilderParameters parameters; + + /** + * Create a new aggregate builder. + */ + public Builder() { + this.parameters = new BuilderParameters(); + } + + /** + * Set the instance provider supplied to all cloners in this builder. + * @param provider - new instance provider. + * @return The current builder. + */ + public Builder instanceProvider(InstanceProvider provider) { + this.parameters.instanceProvider = provider; + return this; + } + + /** + * Add the next cloner that will be considered in turn. + * @param type - the type of the next cloner. + * @return This builder. + */ + public Builder andThen(final Class type) { + // Use reflection to generate a factory on the fly + return andThen(new Function() { + @Override + public Cloner apply(@Nullable BuilderParameters param) { + Object result = param.typeConstructor.create(type); + + if (result == null) { + throw new IllegalStateException("Constructed NULL instead of " + type); + } + + if (type.isAssignableFrom(result.getClass())) + return (Cloner) result; + else + throw new IllegalStateException("Constructed " + result.getClass() + " instead of " + type); + } + }); + } + + /** + * Add the next cloner that will be considered in turn. + * @param factory - factory constructing the next cloner. + * @return This builder. + */ + public Builder andThen(Function factory) { + factories.add(factory); + return this; + } + + /** + * Build a new aggregate cloner using the supplied values. + * @return A new aggregate cloner. + */ + public AggregateCloner build() { + AggregateCloner newCloner = new AggregateCloner(); + + // The parameters we will pass to our cloners + Cloner paramCloner = new NullableCloner(newCloner); + InstanceProvider paramProvider = parameters.instanceProvider; + + // Initialize parameters + parameters.aggregateCloner = paramCloner; + parameters.typeConstructor = DefaultInstances.fromArray( + ExistingGenerator.fromObjectArray(new Object[] { paramCloner, paramProvider }) + ); + + // Build every cloner in the correct order + List cloners = Lists.newArrayList(); + + for (int i = 0; i < factories.size(); i++) { + Cloner cloner = factories.get(i).apply(parameters); + + // See if we were successful + if (cloner != null) + cloners.add(cloner); + else + throw new IllegalArgumentException( + String.format("Cannot create cloner from %s (%s)", factories.get(i), i) + ); + } + + // We're done + newCloner.setCloners(cloners); + return newCloner; + } + } + + /** + * Represents a default aggregate cloner. + */ + public static final AggregateCloner DEFAULT = newBuilder(). + instanceProvider(DefaultInstances.DEFAULT). + andThen(BukkitCloner.class). + andThen(ImmutableDetector.class). + andThen(CollectionCloner.class). + andThen(FieldCloner.class). + build(); + + // List of clone methods + private List cloners; + + private WeakReference lastObject; + private int lastResult; + + /** + * Begins constructing a new aggregate cloner. + * @return A builder for a new aggregate cloner. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Construct a new, empty aggregate cloner. + */ + private AggregateCloner() { + // Only used by our builder above. + } + + /** + * Retrieves a view of the current list of cloners. + * @return Current cloners. + */ + public List getCloners() { + return Collections.unmodifiableList(cloners); + } + + /** + * Set the cloners that will be used. + * @param cloners - the cloners that will be used. + */ + private void setCloners(Iterable cloners) { + this.cloners = Lists.newArrayList(cloners); + } + + @Override + public boolean canClone(Object source) { + // Optimize a bit + lastResult = getFirstCloner(source); + lastObject = new WeakReference(source); + return lastResult >= 0 && lastResult < cloners.size(); + } + + /** + * Retrieve the index of the first cloner capable of cloning the given object. + *

+ * Returns an invalid index if no cloner is able to clone the object. + * @param source - the object to clone. + * @return The index of the cloner object. + */ + private int getFirstCloner(Object source) { + for (int i = 0; i < cloners.size(); i++) { + if (cloners.get(i).canClone(source)) + return i; + } + + return cloners.size(); + } + + @Override + public Object clone(Object source) { + if (source == null) + throw new IllegalAccessError("source cannot be NULL."); + int index = 0; + + // Are we dealing with the same object? + if (lastObject != null && lastObject.get() == source) { + index = lastResult; + } else { + index = getFirstCloner(source); + } + + // Make sure the object is valid + if (index < cloners.size()) { + return cloners.get(index).clone(source); + } + + // Damn - failure + throw new IllegalArgumentException("Cannot clone " + source + ": No cloner is sutable."); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java new file mode 100644 index 00000000..63456619 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java @@ -0,0 +1,73 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.reflect.cloning; + +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.BukkitConverters; +import com.comphenix.protocol.wrappers.ChunkPosition; +import com.comphenix.protocol.wrappers.WrappedDataWatcher; + +/** + * Represents an object that can clone a specific list of Bukkit- and Minecraft-related objects. + * + * @author Kristian + */ +public class BukkitCloner implements Cloner { + // List of classes we support + private Class[] clonableClasses = { MinecraftReflection.getItemStackClass(), MinecraftReflection.getChunkPositionClass(), + MinecraftReflection.getDataWatcherClass() }; + + private int findMatchingClass(Class type) { + // See if is a subclass of any of our supported superclasses + for (int i = 0; i < clonableClasses.length; i++) { + if (clonableClasses[i].isAssignableFrom(type)) + return i; + } + + return -1; + } + + @Override + public boolean canClone(Object source) { + if (source == null) + return false; + + return findMatchingClass(source.getClass()) >= 0; + } + + @Override + public Object clone(Object source) { + if (source == null) + throw new IllegalArgumentException("source cannot be NULL."); + + // Convert to a wrapper + switch (findMatchingClass(source.getClass())) { + case 0: + return MinecraftReflection.getMinecraftItemStack(MinecraftReflection.getBukkitItemStack(source).clone()); + case 1: + EquivalentConverter chunkConverter = ChunkPosition.getConverter(); + return chunkConverter.getGeneric(clonableClasses[1], chunkConverter.getSpecific(source)); + case 2: + EquivalentConverter dataConverter = BukkitConverters.getDataWatcherConverter(); + return dataConverter.getGeneric(clonableClasses[2], dataConverter.getSpecific(source).deepClone()); + default: + throw new IllegalArgumentException("Cannot clone objects of type " + source.getClass()); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/Cloner.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/Cloner.java new file mode 100644 index 00000000..a15455dd --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/Cloner.java @@ -0,0 +1,42 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.reflect.cloning; + +/** + * Represents an object that is capable of cloning other objects. + * + * @author Kristian + */ +public interface Cloner { + /** + * Determine whether or not the current cloner can clone the given object. + * @param source - the object that is being considered. + * @return TRUE if this cloner can actually clone the given object, FALSE otherwise. + */ + public boolean canClone(Object source); + + /** + * Perform the clone. + *

+ * This method should never be called unless a corresponding {@link #canClone(Object)} returns TRUE. + * @param source - the value to clone. + * @return A cloned value. + * @throws IllegalArgumentException If this cloner cannot perform the clone. + */ + public Object clone(Object source); +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/CollectionCloner.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/CollectionCloner.java new file mode 100644 index 00000000..dbb56115 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/CollectionCloner.java @@ -0,0 +1,200 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.reflect.cloning; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Attempts to clone collection and array classes. + * + * @author Kristian + */ +public class CollectionCloner implements Cloner { + private final Cloner defaultCloner; + + /** + * Constructs a new collection and array cloner with the given inner element cloner. + * @param defaultCloner - default inner element cloner. + */ + public CollectionCloner(Cloner defaultCloner) { + this.defaultCloner = defaultCloner; + } + + @Override + public boolean canClone(Object source) { + if (source == null) + return false; + + Class clazz = source.getClass(); + return Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz) || clazz.isArray(); + } + + @SuppressWarnings("unchecked") + @Override + public Object clone(Object source) { + if (source == null) + throw new IllegalArgumentException("source cannot be NULL."); + + Class clazz = source.getClass(); + + if (source instanceof Collection) { + Collection copy = cloneConstructor(Collection.class, clazz, source); + + // Next, clone each element in the collection + copy.clear(); + + for (Object element : (Collection) source) { + copy.add(getClone(element, source)); + } + + return copy; + + } else if (source instanceof Map) { + + Map copy = cloneConstructor(Map.class, clazz, source); + + // Next, clone each element in the collection + copy.clear(); + + for (Entry element : ((Map) source).entrySet()) { + Object key = getClone(element.getKey(), source); + Object value = getClone(element.getValue(), source); + copy.put(key, value); + } + + return copy; + + } else if (clazz.isArray()) { + // Get the length + int lenght = Array.getLength(source); + Class component = clazz.getComponentType(); + + // Can we speed things up by making a shallow copy instead? + if (ImmutableDetector.isImmutable(component)) { + return clonePrimitive(component, source); + } + + // Create a new copy + Object copy = Array.newInstance(clazz.getComponentType(), lenght); + + // Set each element + for (int i = 0; i < lenght; i++) { + Object element = Array.get(source, i); + + if (defaultCloner.canClone(element)) + Array.set(copy, i, defaultCloner.clone(element)); + else + throw new IllegalArgumentException("Cannot clone " + element + " in array " + source); + } + + // And we're done + return copy; + } + + throw new IllegalArgumentException(source + " is not an array nor a Collection."); + } + + /** + * Clone an element using the default cloner. + * @param element - the element to clone. + * @param container - where the element is stored. + * @return The cloned element. + */ + private Object getClone(Object element, Object container) { + if (defaultCloner.canClone(element)) + return defaultCloner.clone(element); + else + throw new IllegalArgumentException("Cannot clone " + element + " in container " + container); + } + + /** + * Clone a primitive or immutable array by calling its clone method. + * @param component - the component type of the array. + * @param source - the array itself. + * @return The cloned array. + */ + private Object clonePrimitive(Class component, Object source) { + // Cast and call the correct version + if (byte.class.equals(component)) + return ((byte[]) source).clone(); + else if (short.class.equals(component)) + return ((short[]) source).clone(); + else if (int.class.equals(component)) + return ((int[]) source).clone(); + else if (long.class.equals(component)) + return ((long[]) source).clone(); + else if (float.class.equals(component)) + return ((float[]) source).clone(); + else if (double.class.equals(component)) + return ((double[]) source).clone(); + else if (char.class.equals(component)) + return ((char[]) source).clone(); + else if (boolean.class.equals(component)) + return ((boolean[]) source).clone(); + else + return ((Object[]) source).clone(); + } + + /** + * Clone an object by calling its clone constructor, or alternatively, a "clone" method. + * @param superclass - the superclass we expect in the clone constructor. + * @param clazz - the class of the object. + * @param source - the object itself. + * @return A cloned object. + */ + @SuppressWarnings("unchecked") + private T cloneConstructor(Class superclass, Class clazz, Object source) { + + // Not all collections or maps implement "clone", but most *do* implement the "copy constructor" pattern + try { + Constructor constructCopy = clazz.getConstructor(Collection.class); + return (T) constructCopy.newInstance(source); + } catch (NoSuchMethodException e) { + return (T) cloneObject(clazz, source); + } catch (Exception e) { + throw new RuntimeException("Cannot construct collection.", e); + } + } + + /** + * Clone an object by calling "clone" using reflection. + * @param clazz - the class type. + * @param obj - the object to clone. + * @return The cloned object. + */ + private Object cloneObject(Class clazz, Object source) { + // Try to clone it instead + try { + return clazz.getMethod("clone").invoke(source); + } catch (Exception e1) { + throw new RuntimeException("Cannot copy " + source + " (" + clazz + ")", e1); + } + } + + /** + * Retrieve the default cloner used to clone the content of each element in the collection. + * @return Cloner used to clone elements. + */ + public Cloner getDefaultCloner() { + return defaultCloner; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/FieldCloner.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/FieldCloner.java new file mode 100644 index 00000000..62a07bc7 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/FieldCloner.java @@ -0,0 +1,105 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.reflect.cloning; + +import com.comphenix.protocol.reflect.ObjectWriter; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.instances.InstanceProvider; + +/** + * Represents a class capable of cloning objects by deeply copying its fields. + * + * @author Kristian + */ +public class FieldCloner implements Cloner { + protected Cloner defaultCloner; + protected InstanceProvider instanceProvider; + + // Used to clone objects + protected ObjectWriter writer; + + /** + * Constructs a field cloner that copies objects by reading and writing the internal fields directly. + * @param defaultCloner - the default cloner used while copying fields. + * @param instanceProvider - used to construct new, empty copies of a given type. + */ + public FieldCloner(Cloner defaultCloner, InstanceProvider instanceProvider) { + this.defaultCloner = defaultCloner; + this.instanceProvider = instanceProvider; + + // Remember to clone the value too + this.writer = new ObjectWriter() { + @Override + protected void transformField(StructureModifier modifierSource, + StructureModifier modifierDest, int fieldIndex) { + defaultTransform(modifierDest, modifierDest, getDefaultCloner(), fieldIndex); + } + }; + } + + /** + * Default implementation of the field transform. Applies a clone operation before a field value is written. + * @param modifierSource - modifier for the original object. + * @param modifierDest - modifier for the new cloned object. + * @param defaultCloner - cloner to use. + * @param fieldIndex - the current field index. + */ + protected void defaultTransform(StructureModifier modifierSource, + StructureModifier modifierDest, Cloner defaultCloner, int fieldIndex) { + + Object value = modifierSource.read(fieldIndex); + modifierDest.write(fieldIndex, defaultCloner.clone(value)); + } + + @Override + public boolean canClone(Object source) { + if (source == null) + return false; + + // Attempt to create the type + return instanceProvider.create(source.getClass()) != null; + } + + @Override + public Object clone(Object source) { + if (source == null) + throw new IllegalArgumentException("source cannot be NULL."); + + Object copy = instanceProvider.create(source.getClass()); + + // Copy public and private fields alike. Skip static fields. + writer.copyTo(source, copy, source.getClass()); + return copy; + } + + /** + * Retrieve the default cloner used to clone the content of each field. + * @return Cloner used to clone fields. + */ + public Cloner getDefaultCloner() { + return defaultCloner; + } + + /** + * Retrieve the instance provider this cloner is using to create new, empty classes. + * @return The instance provider in use. + */ + public InstanceProvider getInstanceProvider() { + return instanceProvider; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/ImmutableDetector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/ImmutableDetector.java new file mode 100644 index 00000000..143e9d04 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/ImmutableDetector.java @@ -0,0 +1,91 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.reflect.cloning; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URL; +import java.security.PublicKey; +import java.util.Locale; +import java.util.UUID; + +import javax.crypto.SecretKey; + +import com.google.common.primitives.Primitives; + +/** + * Detects classes that are immutable, and thus doesn't require cloning. + *

+ * This ought to have no false positives, but plenty of false negatives. + * + * @author Kristian + */ +public class ImmutableDetector implements Cloner { + // Notable immutable classes we might encounter + private static final Class[] immutableClasses = { + StackTraceElement.class, BigDecimal.class, + BigInteger.class, Locale.class, UUID.class, + URL.class, URI.class, Inet4Address.class, + Inet6Address.class, InetSocketAddress.class, + SecretKey.class, PublicKey.class + }; + + @Override + public boolean canClone(Object source) { + // Don't accept NULL + if (source == null) + return false; + + return isImmutable(source.getClass()); + } + + /** + * Determine if the given type is probably immutable. + * @param type - the type to check. + * @return TRUE if the type is immutable, FALSE otherwise. + */ + public static boolean isImmutable(Class type) { + // Cases that are definitely not true + if (type.isArray()) + return false; + + // All primitive types + if (Primitives.isWrapperType(type) || String.class.equals(type)) + return true; + // May not be true, but if so, that kind of code is broken anyways + if (type.isEnum()) + return true; + + for (Class clazz : immutableClasses) + if (clazz.equals(type)) + return true; + + // Probably not + return false; + } + + @Override + public Object clone(Object source) { + // Safe if the class is immutable + return source; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/NullableCloner.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/NullableCloner.java new file mode 100644 index 00000000..2531edf2 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/NullableCloner.java @@ -0,0 +1,49 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.reflect.cloning; + +/** + * Creates a cloner wrapper that accepts and clones NULL values. + * + * @author Kristian + */ +public class NullableCloner implements Cloner { + protected Cloner wrapped; + + public NullableCloner(Cloner wrapped) { + this.wrapped = wrapped; + } + + @Override + public boolean canClone(Object source) { + return true; + } + + @Override + public Object clone(Object source) { + // Don't pass the NULL value to the cloner + if (source == null) + return null; + else + return wrapped.clone(source); + } + + public Cloner getWrapped() { + return wrapped; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java index 38066613..fe7233da 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java @@ -20,6 +20,8 @@ package com.comphenix.protocol.reflect.instances; import java.lang.reflect.Constructor; import java.util.*; +import javax.annotation.Nullable; + import net.sf.cglib.proxy.Enhancer; import com.google.common.base.Objects; @@ -30,7 +32,7 @@ import com.google.common.collect.ImmutableList; * @author Kristian * */ -public class DefaultInstances { +public class DefaultInstances implements InstanceProvider { /** * Standard default instance provider. @@ -326,4 +328,9 @@ public class DefaultInstances { } return false; } + + @Override + public Object create(@Nullable Class type) { + return getDefault(type); + } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/ExistingGenerator.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/ExistingGenerator.java index 43527d17..ca7d85d2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/ExistingGenerator.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/ExistingGenerator.java @@ -18,13 +18,16 @@ package com.comphenix.protocol.reflect.instances; import java.lang.reflect.Field; +import java.util.Collection; import java.util.HashMap; +import java.util.LinkedList; import java.util.Map; import javax.annotation.Nullable; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.google.common.collect.Lists; /** * Provides instance constructors using a list of existing values. @@ -33,8 +36,52 @@ import com.comphenix.protocol.reflect.FuzzyReflection; * @author Kristian */ public class ExistingGenerator implements InstanceProvider { + /** + * Represents a single node in the tree of possible values. + * + * @author Kristian + */ + private static final class Node { + private Map, Node> children; + private Class key; + private Object value; + private int level; + + public Node(Class key, Object value, int level) { + this.children = new HashMap, Node>(); + this.key = key; + this.value = value; + this.level = level; + } - private Map existingValues = new HashMap(); + public Node addChild(Node node) { + children.put(node.key, node); + return node; + } + + public int getLevel() { + return level; + } + + public Collection getChildren() { + return children.values(); + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public Node getChild(Class clazz) { + return children.get(clazz); + } + } + + // Represents the root node + private Node root = new Node(null, null, 0); private ExistingGenerator() { // Only accessible to the constructors @@ -110,18 +157,94 @@ public class ExistingGenerator implements InstanceProvider { if (value == null) throw new IllegalArgumentException("Value cannot be NULL."); - existingValues.put(value.getClass().getName(), value); - } - - private void addObject(Class type, Object value) { - existingValues.put(type.getName(), value); + addObject(value.getClass(), value); } + private void addObject(Class type, Object value) { + Node node = getLeafNode(root, type, false); + + // Set the value + node.setValue(value); + } + + private Node getLeafNode(final Node start, Class type, boolean readOnly) { + Class[] path = getHierachy(type); + Node current = start; + + for (int i = 0; i < path.length; i++) { + Node next = getNext(current, path[i], readOnly); + + // Try every interface too + if (next == null && readOnly) { + current = null; + break; + } + + current = next; + } + + // And we're done + return current; + } + + private Node getNext(Node current, Class clazz, boolean readOnly) { + Node next = current.getChild(clazz); + + // Add a new node if needed + if (next == null && !readOnly) { + next = current.addChild(new Node(clazz, null, current.getLevel() + 1)); + } + + // Add interfaces + if (next != null && !readOnly && !clazz.isInterface()) { + for (Class clazzInterface : clazz.getInterfaces()) { + getLeafNode(root, clazzInterface, readOnly).addChild(next); + } + } + return next; + } + + private Node getLowestLeaf(Node current) { + Node candidate = current; + + // Depth-first search + for (Node child : current.getChildren()) { + Node subtree = getLowestLeaf(child); + + // Get the lowest node + if (subtree.getValue() != null && candidate.getLevel() < subtree.getLevel()) { + candidate = subtree; + } + } + + return candidate; + } + + private Class[] getHierachy(Class type) { + LinkedList> levels = Lists.newLinkedList(); + + // Add each class from the hierachy + for (; type != null; type = type.getSuperclass()) { + levels.addFirst(type); + } + + return levels.toArray(new Class[0]); + } + @Override public Object create(@Nullable Class type) { - Object value = existingValues.get(type.getName()); - + // Locate the type in the hierachy + Node node = getLeafNode(root, type, true); + + // Next, get the lowest leaf node + if (node != null) { + node = getLowestLeaf(node); + } + // NULL values indicate that the generator failed - return value; + if (node != null) + return node.getValue(); + else + return null; } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/CachedPackage.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/CachedPackage.java index e69f956c..9081705e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/CachedPackage.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/CachedPackage.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.utility; import java.util.Map; @@ -18,16 +35,24 @@ class CachedPackage { this.cache = Maps.newConcurrentMap(); } + /** + * Associate a given class with a class name. + * @param className - class name. + * @param clazz - type of class. + */ + public void setPackageClass(String className, Class clazz) { + cache.put(className, clazz); + } + /** * Retrieve the class object of a specific class in the current package. * @param className - the specific class. * @return Class object. * @throws RuntimeException If we are unable to find the given class. */ - @SuppressWarnings("rawtypes") - public Class getPackageClass(String className) { + public Class getPackageClass(String className) { try { - Class result = cache.get(className); + Class result = cache.get(className); // Concurrency is not a problem - we don't care if we look up a class twice if (result == null) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java index cdacdbdd..92af6cca 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.utility; import java.lang.reflect.InvocationTargetException; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index ded29674..31094d57 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.utility; import java.lang.reflect.Array; @@ -11,6 +28,7 @@ import org.bukkit.Server; import org.bukkit.inventory.ItemStack; import com.comphenix.protocol.injector.BukkitUnwrapper; +import com.google.common.base.Joiner; /** * Methods and constants specifically used in conjuction with reflecting Minecraft object. @@ -35,10 +53,14 @@ public class MinecraftReflection { private static CachedPackage craftbukkitPackage; // org.bukkit.craftbukkit - private static Class craftItemStackClass; private static Constructor craftNMSConstructor; private static Constructor craftBukkitConstructor; + // New in 1.4.5 + private static Method craftNMSMethod; + private static Method craftBukkitMethod; + private static boolean craftItemStackFailed; + // net.minecraft.server private static Class itemStackArrayClass; @@ -107,6 +129,24 @@ public class MinecraftReflection { return fullName.substring(0, fullName.lastIndexOf(".")); } + /** + * Dynamically retrieve the Bukkit entity from a given entity. + * @param nmsObject - the NMS entity. + * @return A bukkit entity. + * @throws RuntimeException If we were unable to retrieve the Bukkit entity. + */ + public static Object getBukkitEntity(Object nmsObject) { + if (nmsObject == null) + return null; + + // We will have to do this dynamically, unfortunately + try { + return nmsObject.getClass().getMethod("getBukkitEntity").invoke(nmsObject); + } catch (Exception e) { + throw new RuntimeException("Cannot get Bukkit entity from " + nmsObject, e); + } + } + /** * Determine if a given object can be found within the package net.minecraft.server. * @param obj - the object to test. @@ -133,31 +173,12 @@ public class MinecraftReflection { String javaName = obj.getClass().getName(); return javaName.startsWith(MINECRAFT_PREFIX_PACKAGE) && javaName.endsWith(className); } - - /** - * Dynamically retrieve the Bukkit entity from a given entity. - * @param nmsObject - the NMS entity. - * @return A bukkit entity. - * @throws RuntimeException If we were unable to retrieve the Bukkit entity. - */ - public static Object getBukkitEntity(Object nmsObject) { - if (nmsObject == null) - return null; - - // We will have to do this dynamically, unfortunately - try { - return nmsObject.getClass().getMethod("getBukkitEntity").invoke(nmsObject); - } catch (Exception e) { - throw new RuntimeException("Cannot get Bukkit entity from " + nmsObject, e); - } - } - + /** * Determine if a given object is a ChunkPosition. * @param obj - the object to test. * @return TRUE if it can, FALSE otherwise. */ - @SuppressWarnings("unchecked") public static boolean isChunkPosition(Object obj) { return getChunkPositionClass().isAssignableFrom(obj.getClass()); } @@ -167,7 +188,6 @@ public class MinecraftReflection { * @param obj - the object to test. * @return TRUE if it can, FALSE otherwise. */ - @SuppressWarnings("unchecked") public static boolean isChunkCoordinates(Object obj) { return getChunkCoordinatesClass().isAssignableFrom(obj.getClass()); } @@ -177,7 +197,6 @@ public class MinecraftReflection { * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ - @SuppressWarnings("unchecked") public static boolean isPacketClass(Object obj) { return getPacketClass().isAssignableFrom(obj.getClass()); } @@ -187,7 +206,6 @@ public class MinecraftReflection { * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ - @SuppressWarnings("unchecked") public static boolean isLoginHandler(Object obj) { return getNetLoginHandlerClass().isAssignableFrom(obj.getClass()); } @@ -197,7 +215,6 @@ public class MinecraftReflection { * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ - @SuppressWarnings("unchecked") public static boolean isMinecraftEntity(Object obj) { return getEntityClass().isAssignableFrom(obj.getClass()); } @@ -207,7 +224,6 @@ public class MinecraftReflection { * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ - @SuppressWarnings("unchecked") public static boolean isItemStack(Object value) { return getItemStackClass().isAssignableFrom(value.getClass()); } @@ -217,7 +233,6 @@ public class MinecraftReflection { * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ - @SuppressWarnings("unchecked") public static boolean isMinecraftPlayer(Object obj) { return getEntityPlayerClass().isAssignableFrom(obj.getClass()); } @@ -227,7 +242,6 @@ public class MinecraftReflection { * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ - @SuppressWarnings("unchecked") public static boolean isWatchableObject(Object obj) { return getWatchableObjectClass().isAssignableFrom(obj.getClass()); } @@ -237,7 +251,6 @@ public class MinecraftReflection { * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ - @SuppressWarnings("unchecked") public static boolean isDataWatcher(Object obj) { return getDataWatcherClass().isAssignableFrom(obj.getClass()); } @@ -247,7 +260,6 @@ public class MinecraftReflection { * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ - @SuppressWarnings("unchecked") public static boolean isCraftItemStack(Object obj) { return getCraftItemStackClass().isAssignableFrom(obj.getClass()); } @@ -256,8 +268,7 @@ public class MinecraftReflection { * Retrieve the EntityPlayer (NMS) class. * @return The entity class. */ - @SuppressWarnings("rawtypes") - public static Class getEntityPlayerClass() { + public static Class getEntityPlayerClass() { return getMinecraftClass("EntityPlayer"); } @@ -265,8 +276,7 @@ public class MinecraftReflection { * Retrieve the entity (NMS) class. * @return The entity class. */ - @SuppressWarnings("rawtypes") - public static Class getEntityClass() { + public static Class getEntityClass() { return getMinecraftClass("Entity"); } @@ -274,8 +284,7 @@ public class MinecraftReflection { * Retrieve the packet class. * @return The packet class. */ - @SuppressWarnings("rawtypes") - public static Class getPacketClass() { + public static Class getPacketClass() { return getMinecraftClass("Packet"); } @@ -283,17 +292,39 @@ public class MinecraftReflection { * Retrieve the NetLoginHandler class. * @return The NetLoginHandler class. */ - @SuppressWarnings("rawtypes") - public static Class getNetLoginHandlerClass() { - return getMinecraftClass("NetLoginHandler"); + public static Class getNetLoginHandlerClass() { + return getMinecraftClass("NetLoginHandler", "PendingConnection"); } /** - * Retrieve the NetLoginHandler class. - * @return The NetLoginHandler class. + * Retrieve the NetServerHandler class. + * @return The NetServerHandler class. */ - @SuppressWarnings("rawtypes") - public static Class getItemStackClass() { + public static Class getNetServerHandlerClass() { + return getMinecraftClass("NetServerHandler", "PlayerConnection"); + } + + /** + * Retrieve the NetworkManager class. + * @return The NetworkManager class. + */ + public static Class getNetworkManagerClass() { + return getMinecraftClass("NetworkManager"); + } + + /** + * Retrieve the NetHandler class. + * @return The NetHandler class. + */ + public static Class getNetHandlerClass() { + return getMinecraftClass("NetHandler", "Connection"); + } + + /** + * Retrieve the NMS ItemStack class. + * @return The ItemStack class. + */ + public static Class getItemStackClass() { return getMinecraftClass("ItemStack"); } @@ -301,17 +332,23 @@ public class MinecraftReflection { * Retrieve the WorldType class. * @return The WorldType class. */ - @SuppressWarnings("rawtypes") - public static Class getWorldTypeClass() { + public static Class getWorldTypeClass() { return getMinecraftClass("WorldType"); } + /** + * Retrieve the MinecraftServer class. + * @return MinecraftServer class. + */ + public static Class getMinecraftServerClass() { + return getMinecraftClass("MinecraftServer"); + } + /** * Retrieve the DataWatcher class. * @return The DataWatcher class. */ - @SuppressWarnings("rawtypes") - public static Class getDataWatcherClass() { + public static Class getDataWatcherClass() { return getMinecraftClass("DataWatcher"); } @@ -319,8 +356,7 @@ public class MinecraftReflection { * Retrieve the ChunkPosition class. * @return The ChunkPosition class. */ - @SuppressWarnings("rawtypes") - public static Class getChunkPositionClass() { + public static Class getChunkPositionClass() { return getMinecraftClass("ChunkPosition"); } @@ -328,8 +364,7 @@ public class MinecraftReflection { * Retrieve the ChunkPosition class. * @return The ChunkPosition class. */ - @SuppressWarnings("rawtypes") - public static Class getChunkCoordinatesClass() { + public static Class getChunkCoordinatesClass() { return getMinecraftClass("ChunkCoordinates"); } @@ -337,8 +372,7 @@ public class MinecraftReflection { * Retrieve the WatchableObject class. * @return The WatchableObject class. */ - @SuppressWarnings("rawtypes") - public static Class getWatchableObjectClass() { + public static Class getWatchableObjectClass() { return getMinecraftClass("WatchableObject"); } @@ -346,8 +380,7 @@ public class MinecraftReflection { * Retrieve the ItemStack[] class. * @return The ItemStack[] class. */ - @SuppressWarnings("rawtypes") - public static Class getItemStackArrayClass() { + public static Class getItemStackArrayClass() { if (itemStackArrayClass == null) itemStackArrayClass = getArrayClass(getItemStackClass()); return itemStackArrayClass; @@ -358,8 +391,7 @@ public class MinecraftReflection { * @param componentType - type of each element in the array. * @return The class of the array. */ - @SuppressWarnings("rawtypes") - public static Class getArrayClass(Class componentType) { + public static Class getArrayClass(Class componentType) { // Bit of a hack, but it works return Array.newInstance(componentType, 0).getClass(); } @@ -368,11 +400,8 @@ public class MinecraftReflection { * Retrieve the CraftItemStack class. * @return The CraftItemStack class. */ - @SuppressWarnings("rawtypes") - public static Class getCraftItemStackClass() { - if (craftItemStackClass == null) - craftItemStackClass = getCraftBukkitClass("inventory.CraftItemStack"); - return craftItemStackClass; + public static Class getCraftItemStackClass() { + return getCraftBukkitClass("inventory.CraftItemStack"); } /** @@ -380,12 +409,19 @@ public class MinecraftReflection { * @param bukkitItemStack - the Bukkit ItemStack to convert. * @return A CraftItemStack as an ItemStack. */ - @SuppressWarnings("unchecked") public static ItemStack getBukkitItemStack(ItemStack bukkitItemStack) { + // Delegate this task to the method that can execute it + if (craftBukkitMethod != null) + return getBukkitItemByMethod(bukkitItemStack); + if (craftBukkitConstructor == null) { try { craftBukkitConstructor = getCraftItemStackClass().getConstructor(ItemStack.class); } catch (Exception e) { + // See if this method works + if (!craftItemStackFailed) + return getBukkitItemByMethod(bukkitItemStack); + throw new RuntimeException("Cannot find CraftItemStack(org.bukkit.inventory.ItemStack).", e); } } @@ -397,18 +433,43 @@ public class MinecraftReflection { throw new RuntimeException("Cannot construct CraftItemStack.", e); } } - + + private static ItemStack getBukkitItemByMethod(ItemStack bukkitItemStack) { + if (craftBukkitMethod == null) { + try { + craftBukkitMethod = getCraftItemStackClass().getMethod("asCraftCopy", ItemStack.class); + } catch (Exception e) { + craftItemStackFailed = true; + throw new RuntimeException("Cannot find CraftItemStack.asCraftCopy(org.bukkit.inventory.ItemStack).", e); + } + } + + // Next, construct it + try { + return (ItemStack) craftBukkitMethod.invoke(null, bukkitItemStack); + } catch (Exception e) { + throw new RuntimeException("Cannot construct CraftItemStack.", e); + } + } + /** * Retrieve the Bukkit ItemStack from a given net.minecraft.server ItemStack. * @param minecraftItemStack - the NMS ItemStack to wrap. * @return The wrapped ItemStack. */ - @SuppressWarnings("unchecked") public static ItemStack getBukkitItemStack(Object minecraftItemStack) { + // Delegate this task to the method that can execute it + if (craftNMSMethod != null) + return getBukkitItemByMethod(minecraftItemStack); + if (craftNMSConstructor == null) { try { craftNMSConstructor = getCraftItemStackClass().getConstructor(minecraftItemStack.getClass()); } catch (Exception e) { + // Give it a try + if (!craftItemStackFailed) + return getBukkitItemByMethod(minecraftItemStack); + throw new RuntimeException("Cannot find CraftItemStack(net.mineraft.server.ItemStack).", e); } } @@ -421,6 +482,24 @@ public class MinecraftReflection { } } + private static ItemStack getBukkitItemByMethod(Object minecraftItemStack) { + if (craftNMSMethod == null) { + try { + craftNMSMethod = getCraftItemStackClass().getMethod("asCraftMirror", minecraftItemStack.getClass()); + } catch (Exception e) { + craftItemStackFailed = true; + throw new RuntimeException("Cannot find CraftItemStack.asCraftMirror(net.mineraft.server.ItemStack).", e); + } + } + + // Next, construct it + try { + return (ItemStack) craftNMSMethod.invoke(null, minecraftItemStack); + } catch (Exception e) { + throw new RuntimeException("Cannot construct CraftItemStack.", e); + } + } + /** * Retrieve the net.minecraft.server ItemStack from a Bukkit ItemStack. * @param stack - the Bukkit ItemStack to convert. @@ -454,10 +533,63 @@ public class MinecraftReflection { * @return Class object. * @throws RuntimeException If we are unable to find the given class. */ - @SuppressWarnings("rawtypes") - public static Class getMinecraftClass(String className) { + public static Class getMinecraftClass(String className) { if (minecraftPackage == null) minecraftPackage = new CachedPackage(getMinecraftPackage()); return minecraftPackage.getPackageClass(className); } + + /** + * Retrieve the first class that matches a specified Minecraft name. + * @param classes - the specific Minecraft class. + * @return Class object. + * @throws RuntimeException If we are unable to find any of the given classes. + */ + public static Class getMinecraftClass(String className, String... aliases) { + try { + // Try the main class first + return getMinecraftClass(className); + } catch (RuntimeException e1) { + Class success = null; + + // Try every alias too + for (String alias : aliases) { + try { + success = getMinecraftClass(alias); + break; + } catch (RuntimeException e2) { + // Swallov + } + } + + if (success != null) { + // Save it for later + minecraftPackage.setPackageClass(className, success); + return success; + } else { + // Hack failed + throw new RuntimeException( + String.format("Unable to find %s (%s)", + className, + Joiner.on(", ").join(aliases)) + ); + } + } + } + + /** + * Dynamically retrieve the NetworkManager name. + * @return Name of the NetworkManager class. + */ + public static String getNetworkManagerName() { + return getNetworkManagerClass().getSimpleName(); + } + + /** + * Dynamically retrieve the name of the current NetLoginHandler. + * @return Name of the NetLoginHandler class. + */ + public static String getNetLoginHandlerName() { + return getNetLoginHandlerClass().getSimpleName(); + } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java index 7b9bfe49..7d6f421e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.wrappers; import java.lang.ref.WeakReference; @@ -155,7 +172,6 @@ public class BukkitConverters { return null; return getIgnoreNull(new EquivalentConverter() { - @SuppressWarnings("unchecked") @Override public Object getGeneric(Class genericType, WorldType specific) { try { @@ -169,8 +185,7 @@ public class BukkitConverters { throw new FieldAccessException("Cannot find the WorldType.getType() method.", e); } } - - @SuppressWarnings("unchecked") + @Override public WorldType getSpecific(Object generic) { try { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java index fd7bc679..d5326a85 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.wrappers; import java.lang.reflect.Constructor; @@ -133,7 +150,6 @@ public class ChunkPosition { */ public static EquivalentConverter getConverter() { return new EquivalentConverter() { - @SuppressWarnings("unchecked") @Override public Object getGeneric(Class genericType, ChunkPosition specific) { if (chunkPositionConstructor == null) { @@ -166,7 +182,8 @@ public class ChunkPosition { if (intModifier.size() >= 3) { try { - return new ChunkPosition(intModifier.read(0), intModifier.read(1), intModifier.read(2)); + StructureModifier instance = intModifier.withTarget(generic); + return new ChunkPosition(instance.read(0), instance.read(1), instance.read(2)); } catch (FieldAccessException e) { // This is an exeptional work-around, so we don't want to burden the caller with the messy details throw new RuntimeException("Field access error.", e); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java index 2b6bb244..e673eec4 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.wrappers; import com.comphenix.protocol.reflect.StructureModifier; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java index 90eeb283..012d9458 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.wrappers; import java.lang.reflect.Field; @@ -275,6 +292,37 @@ public class WrappedDataWatcher implements Iterable { } } + @Override + public boolean equals(Object obj) { + // Quick checks + if (obj == this) + return true; + if (obj == null) + return false; + + if (obj instanceof WrappedDataWatcher) { + WrappedDataWatcher other = (WrappedDataWatcher) obj; + Iterator first = iterator(), second = other.iterator(); + + // Make sure they're the same size + if (size() != other.size()) + return false; + + for (; first.hasNext() && second.hasNext(); ) { + // See if the two elements are equal + if (!first.next().equals(second.next())) + return false; + } + return true; + } + return false; + } + + @Override + public int hashCode() { + return getWatchableObjects().hashCode(); + } + /** * Retrieve a copy of every index associated with a watched object. * @return Every watched object index. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java index c3af7039..c1b2edb7 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java @@ -1,3 +1,20 @@ +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + package com.comphenix.protocol.wrappers; import java.lang.reflect.Constructor; @@ -49,7 +66,6 @@ public class WrappedWatchableObject { * @param index - the index. * @param value - non-null value of specific types. */ - @SuppressWarnings("unchecked") public WrappedWatchableObject(int index, Object value) { if (value == null) throw new IllegalArgumentException("Value cannot be NULL."); @@ -296,8 +312,8 @@ public class WrappedWatchableObject { * @throws FieldAccessException If we're unable to use reflection. */ public WrappedWatchableObject deepClone() throws FieldAccessException { - @SuppressWarnings("unchecked") - WrappedWatchableObject clone = new WrappedWatchableObject(DefaultInstances.DEFAULT.getDefault(MinecraftReflection.getWatchableObjectClass())); + WrappedWatchableObject clone = new WrappedWatchableObject( + DefaultInstances.DEFAULT.getDefault(MinecraftReflection.getWatchableObjectClass())); clone.setDirtyState(getDirtyState()); clone.setIndex(getIndex()); diff --git a/ProtocolLib/src/main/resources/config.yml b/ProtocolLib/src/main/resources/config.yml index 0b5a4f64..32f4e4c3 100644 --- a/ProtocolLib/src/main/resources/config.yml +++ b/ProtocolLib/src/main/resources/config.yml @@ -2,7 +2,7 @@ global: # Settings for the automatic version updater auto updater: notify: true - download: true + download: false # Number of seconds to wait until a new update is downloaded delay: 43200 # 12 hours diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index cd537899..68783116 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 1.8.0 +version: 1.9.0 description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java new file mode 100644 index 00000000..bbee0a76 --- /dev/null +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -0,0 +1,382 @@ +package com.comphenix.protocol.events; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Array; +import java.util.List; + +// Will have to be updated for every version though +import org.bukkit.craftbukkit.v1_4_6.inventory.CraftItemFactory; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.WorldType; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; + +import com.comphenix.protocol.Packets; +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.BukkitConverters; +import com.comphenix.protocol.wrappers.ChunkPosition; +import com.comphenix.protocol.wrappers.WrappedDataWatcher; +import com.comphenix.protocol.wrappers.WrappedWatchableObject; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +// Ensure that the CraftItemFactory is mockable +@RunWith(org.powermock.modules.junit4.PowerMockRunner.class) +@PrepareForTest(CraftItemFactory.class) +public class PacketContainerTest { + // Helper converters + private EquivalentConverter watchConvert = BukkitConverters.getDataWatcherConverter(); + private EquivalentConverter itemConvert = BukkitConverters.getItemStackConverter(); + + @BeforeClass + public static void initializeBukkit() throws IllegalAccessException { + // Initialize reflection + MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_4_6", "org.bukkit.craftbukkit.v1_4_6"); + + // Mock the server object + Server mockedServer = mock(Server.class); + ItemFactory mockedFactory = mock(CraftItemFactory.class); + ItemMeta mockedMeta = mock(ItemMeta.class); + + when(mockedServer.getItemFactory()).thenReturn(mockedFactory); + when(mockedFactory.getItemMeta(any(Material.class))).thenReturn(mockedMeta); + + // Inject this fake server + FieldUtils.writeStaticField(Bukkit.class, "server", mockedServer, true); + + // And the fake item factory + FieldUtils.writeStaticField(CraftItemFactory.class, "instance", mockedFactory, true); + } + + private void testPrimitive(StructureModifier modifier, int index, T initialValue, T testValue) { + // Check initial value + assertEquals(initialValue, modifier.read(index)); + + // Test assignment + modifier.write(index, testValue); + assertEquals(testValue, modifier.read(0)); + } + + private void testObjectArray(StructureModifier modifier, int index, T[] initialValue, T[] testValue) { + // Check initial value + assertNull(modifier.read(index)); + modifier.writeDefaults(); + + // Test initial + assertArrayEquals(initialValue, modifier.read(index)); + + // Test assignment + modifier.write(index, testValue); + assertArrayEquals(testValue, modifier.read(0)); + } + + @Test + public void testGetByteArrays() { + // Contains a byte array we will test + PacketContainer customPayload = new PacketContainer(Packets.Server.CUSTOM_PAYLOAD); + StructureModifier bytes = customPayload.getByteArrays(); + byte[] testArray = new byte[] { 1, 2, 3 }; + + // It's NULL at first + assertArrayEquals(null, (byte[]) bytes.read(0)); + customPayload.getModifier().writeDefaults(); + + // Then it should create an empty array + assertArrayEquals(new byte[0], (byte[]) bytes.read(0)); + + // Check and see if we can write to it + bytes.write(0, testArray); + assertArrayEquals(testArray, (byte[]) bytes.read(0)); + } + + @Test + public void testGetBytes() { + PacketContainer spawnMob = new PacketContainer(Packets.Server.MOB_SPAWN); + testPrimitive(spawnMob.getBytes(), 0, (byte)0, (byte)1); + } + + @Test + public void testGetShorts() { + PacketContainer itemData = new PacketContainer(Packets.Server.ITEM_DATA); + testPrimitive(itemData.getShorts(), 0, (short)0, (short)1); + } + + @Test + public void testGetIntegers() { + PacketContainer updateSign = new PacketContainer(Packets.Server.UPDATE_SIGN); + testPrimitive(updateSign.getIntegers(), 0, (int)0, (int)1); + } + + @Test + public void testGetLongs() { + PacketContainer updateTime = new PacketContainer(Packets.Server.UPDATE_TIME); + testPrimitive(updateTime.getLongs(), 0, (long)0, (long)1); + } + + @Test + public void testGetFloat() { + PacketContainer explosion = new PacketContainer(Packets.Server.EXPLOSION); + testPrimitive(explosion.getFloat(), 0, (float)0, (float)0.8); + } + + @Test + public void testGetDoubles() { + PacketContainer explosion = new PacketContainer(Packets.Server.EXPLOSION); + testPrimitive(explosion.getDoubles(), 0, (double)0, (double)0.8); + } + + @Test + public void testGetStrings() { + PacketContainer explosion = new PacketContainer(Packets.Server.CHAT); + testPrimitive(explosion.getStrings(), 0, null, "hello"); + } + + @Test + public void testGetStringArrays() { + PacketContainer explosion = new PacketContainer(Packets.Server.UPDATE_SIGN); + testObjectArray(explosion.getStringArrays(), 0, new String[0], new String[] { "hello", "world" }); + } + + @Test + public void testGetIntegerArrays() { + // Contains a byte array we will test + PacketContainer mapChunkBulk = new PacketContainer(Packets.Server.MAP_CHUNK_BULK); + StructureModifier integers = mapChunkBulk.getIntegerArrays(); + int[] testArray = new int[] { 1, 2, 3 }; + + // Pre and post conditions + assertArrayEquals(null, (int[]) integers.read(0)); + mapChunkBulk.getModifier().writeDefaults(); + assertArrayEquals(new int[0], (int[]) integers.read(0)); + + integers.write(0, testArray); + assertArrayEquals(testArray, (int[]) integers.read(0)); + } + + @Test + public void testGetItemModifier() { + PacketContainer windowClick = new PacketContainer(Packets.Client.WINDOW_CLICK); + + StructureModifier items = windowClick.getItemModifier(); + ItemStack goldAxe = new ItemStack(Material.GOLD_AXE); + + assertNull(items.read(0)); + + // Insert the goldaxe and check if it's there + items.write(0, goldAxe); + assertTrue(equivalentItem(goldAxe, items.read(0))); + } + + @Test + public void testGetItemArrayModifier() { + PacketContainer windowItems = new PacketContainer(Packets.Server.WINDOW_ITEMS); + StructureModifier itemAccess = windowItems.getItemArrayModifier(); + + ItemStack[] itemArray = new ItemStack[] { + new ItemStack(Material.GOLD_AXE), + new ItemStack(Material.DIAMOND_AXE) + }; + + assertNull(itemAccess.read(0)); + + // Insert and check that it was succesful + itemAccess.write(0, itemArray); + + // Read back array + ItemStack[] comparision = itemAccess.read(0); + assertEquals(itemArray.length, comparision.length); + + // Check that it is equivalent + for (int i = 0; i < itemArray.length; i++) { + assertTrue(String.format("Array element %s is not the same: %s != %s", + i, itemArray[i], comparision[i]), equivalentItem(itemArray[i], comparision[i])); + } + } + + private boolean equivalentItem(ItemStack first, ItemStack second) { + if (first == null) { + return second == null; + } else if (second == null) { + return false; + } else { + return first.getType().equals(second.getType()); + } + } + + @Test + public void testGetWorldTypeModifier() { + PacketContainer loginPacket = new PacketContainer(Packets.Server.LOGIN); + StructureModifier worldAccess = loginPacket.getWorldTypeModifier(); + + WorldType testValue = WorldType.LARGE_BIOMES; + + assertNull(worldAccess.read(0)); + + // Insert and read back + worldAccess.write(0, testValue); + assertEquals(testValue, worldAccess.read(0)); + } + + @Test + public void testGetDataWatcherModifier() { + PacketContainer mobSpawnPacket = new PacketContainer(Packets.Server.MOB_SPAWN); + StructureModifier watcherAccessor = mobSpawnPacket.getDataWatcherModifier(); + + WrappedDataWatcher dataWatcher = new WrappedDataWatcher(); + dataWatcher.setObject(1, 100); + dataWatcher.setObject(2, 125); + + assertNull(watcherAccessor.read(0)); + + // Insert and read back + watcherAccessor.write(0, dataWatcher); + assertEquals(dataWatcher, watcherAccessor.read(0)); + } + + // Unfortunately, it might be too difficult to mock this one + // + // @Test + // public void testGetEntityModifier() { } + + // No packet expose this type directly. + // + // @Test + // public void testGetPositionModifier() { } + + @Test + public void testGetPositionCollectionModifier() { + PacketContainer explosionPacket = new PacketContainer(Packets.Server.EXPLOSION); + StructureModifier> positionAccessor = explosionPacket.getPositionCollectionModifier(); + + assertNull(positionAccessor.read(0)); + + List positions = Lists.newArrayList(); + positions.add(new ChunkPosition(1, 2, 3)); + positions.add(new ChunkPosition(3, 4, 5)); + + // Insert and read back + positionAccessor.write(0, positions); + assertEquals(positions, positionAccessor.read(0)); + } + + @Test + public void testGetWatchableCollectionModifier() { + PacketContainer entityMetadata = new PacketContainer(Packets.Server.ENTITY_METADATA); + StructureModifier> watchableAccessor = + entityMetadata.getWatchableCollectionModifier(); + + assertNull(watchableAccessor.read(0)); + + WrappedDataWatcher watcher = new WrappedDataWatcher(); + watcher.setObject(1, 10); + watcher.setObject(8, 10); + + List list = watcher.getWatchableObjects(); + + // Insert and read back + watchableAccessor.write(0, list); + assertEquals(list, watchableAccessor.read(0)); + } + + @Test + public void testDeepClone() { + // Try constructing all the packets + for (Integer id : Iterables.concat( + Packets.getClientRegistry().values(), + Packets.getServerRegistry().values() )) { + + // Whether or not this packet has been registered + boolean registered = Packets.Server.isSupported(id) || + Packets.Client.isSupported(id); + + try { + PacketContainer constructed = new PacketContainer(id); + + if (!registered) { + fail("Expected IllegalArgumentException(Packet " + id + " not registered"); + } + + // Make sure these packets contains fields as well + assertTrue("Constructed packet with no known fields (" + id + ")", + constructed.getModifier().size() > 0); + + // Initialize default values + constructed.getModifier().writeDefaults(); + + // Clone the packet + PacketContainer cloned = constructed.deepClone(); + + // Make sure they're equivalent + StructureModifier firstMod = constructed.getModifier(), secondMod = cloned.getModifier(); + assertEquals(firstMod.size(), secondMod.size()); + + // Make sure all the fields are equivalent + for (int i = 0; i < firstMod.size(); i++) { + if (firstMod.getField(i).getType().isArray()) + assertArrayEquals(getArray(firstMod.read(i)), getArray(secondMod.read(i))); + else + testEquality(firstMod.read(i), secondMod.read(i)); + } + + } catch (IllegalArgumentException e) { + if (!registered) { + // Check the same + assertEquals(e.getMessage(), "The packet ID " + id + " is not registered."); + } else { + // Something is very wrong + throw e; + } + } + } + } + + // Convert to objects that support equals() + private void testEquality(Object a, Object b) { + if (a != null && b != null) { + if (MinecraftReflection.isDataWatcher(a)) { + a = watchConvert.getSpecific(a); + b = watchConvert.getSpecific(b); + } else if (MinecraftReflection.isItemStack(a)) { + a = itemConvert.getSpecific(a); + b = itemConvert.getSpecific(b); + } + } + + assertEquals(a, b); + } + + /** + * Get the underlying array as an object array. + * @param val - array wrapped as an Object. + * @return An object array. + */ + private Object[] getArray(Object val) { + if (val instanceof Object[]) + return (Object[]) val; + if (val == null) + return null; + + int arrlength = Array.getLength(val); + Object[] outputArray = new Object[arrlength]; + + for (int i = 0; i < arrlength; ++i) + outputArray[i] = Array.get(val, i); + return outputArray; + } +}