From c5f05509531eb22e9a0580f07bc0bf17a901b729 Mon Sep 17 00:00:00 2001 From: Pasqual Koschmieder Date: Sun, 24 Jul 2022 16:16:05 +0200 Subject: [PATCH] Use MethodHandles for reflection (#1561) * don't enforce async calls for thread-safe listeners (closes #1551) * cleanups, remove structure compiling * improve cloning a bit * fix small issue in no-op structure modifier * remove last usages of FieldUtils * improve and fix equality check in container test --- .../com/comphenix/protocol/CommandPacket.java | 16 +- .../com/comphenix/protocol/PacketType.java | 3 +- .../comphenix/protocol/ProtocolConfig.java | 10 - .../com/comphenix/protocol/ProtocolLib.java | 29 +- .../comphenix/protocol/ProtocolLibrary.java | 32 +- .../protocol/events/AbstractStructure.java | 26 - .../protocol/events/PacketContainer.java | 8 - .../protocol/injector/BukkitUnwrapper.java | 21 +- .../protocol/injector/EntityUtilities.java | 6 +- .../protocol/injector/StructureCache.java | 46 +- .../injector/netty/NettyByteBufAdapter.java | 118 +- .../protocol/injector/netty/WirePacket.java | 111 +- .../netty/channel/NettyChannelInjector.java | 9 +- .../netty/manager/NetworkManagerInjector.java | 2 +- .../injector/packet/MapContainer.java | 19 +- .../injector/packet/PacketRegistry.java | 2 +- .../protocol/reflect/ClassAnalyser.java | 135 -- .../protocol/reflect/EquivalentConverter.java | 20 +- .../protocol/reflect/ExactReflection.java | 240 ++- .../reflect/FieldAccessException.java | 6 +- .../protocol/reflect/FieldUtils.java | 510 ------ .../protocol/reflect/FuzzyReflection.java | 672 ++++---- .../comphenix/protocol/reflect/IntEnum.java | 106 -- .../protocol/reflect/MethodInfo.java | 94 +- .../protocol/reflect/MethodUtils.java | 1327 --------------- .../protocol/reflect/ObjectWriter.java | 112 +- .../protocol/reflect/PrettyPrinter.java | 200 +-- .../protocol/reflect/StructureModifier.java | 998 ++++++------ .../protocol/reflect/VolatileField.java | 243 --- .../protocol/reflect/accessors/Accessors.java | 350 ++-- .../accessors/ConstructorAccessor.java | 9 +- .../accessors/DefaultConstrutorAccessor.java | 51 +- .../accessors/DefaultFieldAccessor.java | 70 +- .../accessors/DefaultMethodAccessor.java | 51 +- .../reflect/accessors/FieldAccessor.java | 17 +- .../accessors/MemorizingFieldAccessor.java | 36 + .../reflect/accessors/MethodAccessor.java | 14 +- .../reflect/accessors/MethodHandleHelper.java | 121 ++ .../accessors/ReadOnlyFieldAccessor.java | 8 - .../reflect/accessors/UnsafeFieldAccess.java | 48 - .../reflect/cloning/BukkitCloner.java | 5 +- .../reflect/compiler/BackgroundCompiler.java | 385 ----- .../reflect/compiler/BoxingHelper.java | 294 ---- .../reflect/compiler/CompileListener.java | 34 - .../compiler/CompiledStructureModifier.java | 134 -- .../reflect/compiler/MethodDescriptor.java | 237 --- .../reflect/compiler/StructureCompiler.java | 571 ------- .../reflect/fuzzy/AbstractFuzzyMatcher.java | 137 +- .../reflect/fuzzy/AbstractFuzzyMember.java | 433 ++--- .../reflect/fuzzy/ClassExactMatcher.java | 146 -- .../reflect/fuzzy/ClassRegexMatcher.java | 49 +- .../reflect/fuzzy/ClassSetMatcher.java | 44 +- .../reflect/fuzzy/ClassTypeMatcher.java | 114 ++ .../reflect/fuzzy/FuzzyClassContract.java | 440 +++-- .../reflect/fuzzy/FuzzyFieldContract.java | 291 ++-- .../protocol/reflect/fuzzy/FuzzyMatchers.java | 150 +- .../reflect/fuzzy/FuzzyMethodContract.java | 572 ++++--- .../reflect/instances/ExistingGenerator.java | 89 +- .../reflect/instances/MinecraftGenerator.java | 130 +- .../protocol/utility/ByteBuddyFactory.java | 53 +- .../protocol/utility/ByteBuddyGenerated.java | 1 + .../utility/ByteBufferInputStream.java | 35 - .../utility/ByteBufferOutputStream.java | 26 - .../protocol/utility/CachedPackage.java | 90 +- .../protocol/utility/ChatExtensions.java | 154 +- .../protocol/utility/ClassSource.java | 143 +- .../comphenix/protocol/utility/Closer.java | 41 +- .../comphenix/protocol/utility/Constants.java | 33 - .../protocol/utility/MinecraftFields.java | 30 +- .../protocol/utility/MinecraftMethods.java | 253 ++- .../utility/MinecraftProtocolVersion.java | 18 +- .../protocol/utility/MinecraftReflection.java | 1442 ++++------------ .../protocol/utility/MinecraftVersion.java | 534 +++--- .../protocol/utility/NettyVersion.java | 87 - .../protocol/utility/ObjectReconstructor.java | 75 - .../protocol/utility/RemappedClassSource.java | 155 -- .../protocol/utility/SnapshotVersion.java | 75 +- .../protocol/utility/StreamSerializer.java | 459 ++---- .../com/comphenix/protocol/utility/Util.java | 29 +- .../protocol/utility/ZeroBuffer.java | 1450 ++++++++--------- .../protocol/wrappers/AutoWrapper.java | 4 +- .../protocol/wrappers/BukkitConverters.java | 20 +- .../protocol/wrappers/ChunkCoordIntPair.java | 9 - .../protocol/wrappers/ChunkPosition.java | 234 --- .../protocol/wrappers/EnumWrappers.java | 10 +- .../protocol/wrappers/TroveWrapper.java | 70 +- .../protocol/wrappers/WrappedBlockData.java | 4 +- .../wrappers/WrappedChatComponent.java | 11 +- .../wrappers/WrappedChunkCoordinate.java | 167 -- .../protocol/wrappers/WrappedDataWatcher.java | 12 +- .../protocol/wrappers/WrappedGameProfile.java | 10 +- .../protocol/wrappers/WrappedServerPing.java | 2 +- .../protocol/wrappers/WrappedStatistic.java | 4 +- .../wrappers/WrappedWatchableObject.java | 321 ++-- .../protocol/wrappers/nbt/NbtFactory.java | 2 +- .../wrappers/nbt/TileEntityAccessor.java | 246 +-- .../protocol/wrappers/nbt/WrappedElement.java | 4 +- .../wrappers/nbt/io/NbtBinarySerializer.java | 181 +- .../protocol/SimpleCraftBukkitITCase.java | 15 +- .../protocol/BukkitInitialization.java | 10 +- .../comphenix/protocol/PacketTypeTest.java | 4 +- .../protocol/events/PacketContainerTest.java | 55 +- .../reflect/accessors/AccessorsTest.java | 24 +- .../utility/MinecraftReflectionTestUtil.java | 14 + .../comphenix/protocol/utility/TestUtils.java | 2 +- .../protocol/wrappers/EnumWrappersTest.java | 2 +- 106 files changed, 5112 insertions(+), 11659 deletions(-) delete mode 100644 src/main/java/com/comphenix/protocol/reflect/ClassAnalyser.java delete mode 100644 src/main/java/com/comphenix/protocol/reflect/FieldUtils.java delete mode 100644 src/main/java/com/comphenix/protocol/reflect/IntEnum.java delete mode 100644 src/main/java/com/comphenix/protocol/reflect/MethodUtils.java delete mode 100644 src/main/java/com/comphenix/protocol/reflect/VolatileField.java create mode 100644 src/main/java/com/comphenix/protocol/reflect/accessors/MemorizingFieldAccessor.java create mode 100644 src/main/java/com/comphenix/protocol/reflect/accessors/MethodHandleHelper.java delete mode 100644 src/main/java/com/comphenix/protocol/reflect/accessors/ReadOnlyFieldAccessor.java delete mode 100644 src/main/java/com/comphenix/protocol/reflect/accessors/UnsafeFieldAccess.java delete mode 100644 src/main/java/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java delete mode 100644 src/main/java/com/comphenix/protocol/reflect/compiler/BoxingHelper.java delete mode 100644 src/main/java/com/comphenix/protocol/reflect/compiler/CompileListener.java delete mode 100644 src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java delete mode 100644 src/main/java/com/comphenix/protocol/reflect/compiler/MethodDescriptor.java delete mode 100644 src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java delete mode 100644 src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassExactMatcher.java create mode 100644 src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassTypeMatcher.java delete mode 100644 src/main/java/com/comphenix/protocol/utility/ByteBufferInputStream.java delete mode 100644 src/main/java/com/comphenix/protocol/utility/ByteBufferOutputStream.java delete mode 100644 src/main/java/com/comphenix/protocol/utility/Constants.java delete mode 100644 src/main/java/com/comphenix/protocol/utility/NettyVersion.java delete mode 100644 src/main/java/com/comphenix/protocol/utility/ObjectReconstructor.java delete mode 100644 src/main/java/com/comphenix/protocol/utility/RemappedClassSource.java delete mode 100644 src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java delete mode 100644 src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java create mode 100644 src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTestUtil.java diff --git a/src/main/java/com/comphenix/protocol/CommandPacket.java b/src/main/java/com/comphenix/protocol/CommandPacket.java index b095d98e..08b5c9e3 100644 --- a/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -114,13 +114,7 @@ class CommandPacket extends CommandBase { * @return TRUE if the message was sent successfully, FALSE otherwise. */ public void sendMessageSilently(CommandSender receiver, String message) { - try { - chatter.sendMessageSilently(receiver, message); - } catch (InvocationTargetException e) { - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(receiver, message) - ); - } + chatter.sendMessageSilently(receiver, message); } /** @@ -129,13 +123,7 @@ class CommandPacket extends CommandBase { * @param permission - permission required to receieve the message. NULL to target everyone. */ public void broadcastMessageSilently(String message, String permission) { - try { - chatter.broadcastMessageSilently(message, permission); - } catch (InvocationTargetException e) { - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(message, permission) - ); - } + chatter.broadcastMessageSilently(message, permission); } private void printPage(CommandSender sender, int pageIndex) { diff --git a/src/main/java/com/comphenix/protocol/PacketType.java b/src/main/java/com/comphenix/protocol/PacketType.java index 214d92f8..05adef2d 100644 --- a/src/main/java/com/comphenix/protocol/PacketType.java +++ b/src/main/java/com/comphenix/protocol/PacketType.java @@ -11,7 +11,6 @@ import java.util.function.Consumer; import com.comphenix.protocol.PacketTypeLookup.ClassLookup; import com.comphenix.protocol.events.ConnectionSide; import com.comphenix.protocol.injector.packet.PacketRegistry; -import com.comphenix.protocol.utility.Constants; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; import com.google.common.base.Preconditions; @@ -681,7 +680,7 @@ public class PacketType implements Serializable, Cloneable, Comparable= 0) { this.getServer().getScheduler().cancelTask(this.packetTask); diff --git a/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index c88c2630..e03a9096 100644 --- a/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -1,34 +1,32 @@ /** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2016 dmulloy2 - * - * 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 + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2016 dmulloy2 + *

+ * 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 com.comphenix.protocol.error.BasicErrorReporter; import com.comphenix.protocol.error.ErrorReporter; +import java.util.List; import com.google.common.collect.ImmutableList; import org.apache.commons.lang.Validate; import org.bukkit.plugin.Plugin; -import java.util.List; - /** * The main entry point for ProtocolLib. * @author dmulloy2 */ public class ProtocolLibrary { + /** * The minimum version ProtocolLib has been tested with. */ @@ -106,7 +104,7 @@ public class ProtocolLibrary { } /** - * Whether or not updates are currently disabled. + * Whether updates are currently disabled. * @return True if it is, false if not */ public static boolean updatesDisabled() { diff --git a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java index cd7bc6e2..e132d99b 100644 --- a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java +++ b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java @@ -296,17 +296,6 @@ public abstract class AbstractStructure { BukkitConverters.getEntityTypeConverter()); } - /** - * Retrieves a read/write structure for chunk positions. - * @return A modifier for a ChunkPosition. - */ - public StructureModifier getPositionModifier() { - // Convert to and from the Bukkit wrapper - return structureModifier.withType( - MinecraftReflection.getChunkPositionClass(), - ChunkPosition.getConverter()); - } - /** * Retrieves a read/write structure for block positions. * @return A modifier for a BlockPosition. @@ -378,21 +367,6 @@ public abstract class AbstractStructure { ); } - /** - * Retrieves a read/write structure for collections of chunk positions. - *

- * This modifier will automatically marshal between the visible ProtocolLib ChunkPosition and the - * internal Minecraft ChunkPosition. - * - * @return A modifier for ChunkPosition list fields. - */ - public StructureModifier> getPositionCollectionModifier() { - // Convert to and from the ProtocolLib wrapper - return structureModifier.withType( - Collection.class, - BukkitConverters.getListConverter(ChunkPosition.getConverter())); - } - /** * Retrieves a read/write structure for collections of chunk positions. *

diff --git a/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 761b2d15..3afafb5c 100644 --- a/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -280,10 +280,6 @@ public class PacketContainer extends AbstractStructure implements Serializable { buffer.readBytes(output, buffer.readableBytes()); } catch (IllegalArgumentException e) { throw new IOException("Minecraft packet doesn't support DataOutputStream", e); - } catch (IllegalAccessException e) { - throw new RuntimeException("Insufficient security privileges.", e); - } catch (InvocationTargetException e) { - throw new IOException("Could not serialize Minecraft packet.", e); } } @@ -332,10 +328,6 @@ public class PacketContainer extends AbstractStructure implements Serializable { MinecraftMethods.getPacketReadByteBufMethod().invoke(handle, buffer); } catch (IllegalArgumentException e) { throw new IOException("Minecraft packet doesn't support DataInputStream", e); - } catch (IllegalAccessException e) { - throw new RuntimeException("Insufficient security privileges.", e); - } catch (InvocationTargetException e) { - throw new IOException("Could not deserialize Minecraft packet.", e); } } diff --git a/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java b/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java index 440f91a7..8c9605df 100644 --- a/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java +++ b/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java @@ -22,11 +22,11 @@ import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.injector.PacketConstructor.Unwrapper; -import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.primitives.Primitives; -import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; @@ -255,21 +255,22 @@ public class BukkitUnwrapper implements Unwrapper { * @return The cached field unwrapper. */ private Unwrapper getFieldUnwrapper(final Class type) { - final Field find = FieldUtils.getField(type, "handle", true); - // See if we succeeded - if (find != null) { + FieldAccessor accessor = Accessors.getFieldAccessorOrNull(type, "handle", null); + if (accessor != null) { Unwrapper fieldUnwrapper = new Unwrapper() { @Override public Object unwrapItem(Object wrappedObject) { try { if (wrappedObject instanceof Class) { - return checkClass((Class) wrappedObject, type, find.getType()); + return checkClass((Class) wrappedObject, type, accessor.getField().getType()); } - return FieldUtils.readField(find, wrappedObject, true); - } catch (IllegalAccessException e) { + + return accessor.get(wrappedObject); + } catch (IllegalStateException e) { reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find) + Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e) + .callerParam(wrappedObject, accessor.getField()) ); return null; } @@ -282,7 +283,7 @@ public class BukkitUnwrapper implements Unwrapper { } else { // Inform about this too reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find) + Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(type) ); return null; } diff --git a/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java b/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java index 1c8418e7..2788a1a9 100644 --- a/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java +++ b/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java @@ -88,10 +88,10 @@ class EntityUtilities { public Entity getEntity(World world, int id) { Object level = BukkitUnwrapper.getInstance().unwrapItem(world); if (getEntity == null) { - Method entityGetter = FuzzyReflection.fromObject(level).getMethodByParameters( + Method entityGetter = FuzzyReflection.fromObject(level).getMethodByReturnTypeAndParameters( "getEntity", MinecraftReflection.getEntityClass(), - new Class[]{int.class}); + int.class); getEntity = Accessors.getMethodAccessor(entityGetter); } @@ -100,7 +100,7 @@ class EntityUtilities { } private MethodAccessor findScanPlayers(Class trackerClass) { - MethodAccessor candidate = Accessors.getMethodAcccessorOrNull(trackerClass, "scanPlayers"); + MethodAccessor candidate = Accessors.getMethodAccessorOrNull(trackerClass, "scanPlayers"); if (candidate != null) { return candidate; } diff --git a/src/main/java/com/comphenix/protocol/injector/StructureCache.java b/src/main/java/com/comphenix/protocol/injector/StructureCache.java index c1bf6566..deca2e70 100644 --- a/src/main/java/com/comphenix/protocol/injector/StructureCache.java +++ b/src/main/java/com/comphenix/protocol/injector/StructureCache.java @@ -22,8 +22,6 @@ import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; -import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; -import com.comphenix.protocol.reflect.compiler.CompiledStructureModifier; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.utility.ByteBuddyFactory; import com.comphenix.protocol.utility.MinecraftMethods; @@ -32,9 +30,7 @@ import com.comphenix.protocol.utility.ZeroBuffer; import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.google.common.base.Preconditions; import io.netty.buffer.ByteBuf; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; @@ -48,9 +44,6 @@ import net.bytebuddy.matcher.ElementMatchers; */ public class StructureCache { - // prevent duplicate compilations - private static final Set COMPILING = new HashSet<>(); - // Structure modifiers private static final Map, Supplier> PACKET_INSTANCE_CREATORS = new ConcurrentHashMap<>(); private static final Map> STRUCTURE_MODIFIER_CACHE = new ConcurrentHashMap<>(); @@ -98,17 +91,6 @@ public class StructureCache { return newPacket(PacketRegistry.getPacketClassFromType(type)); } - /** - * Retrieve a cached structure modifier for the given packet type. - * - * @param type - packet type. - * @return A structure modifier. - */ - public static StructureModifier getStructure(PacketType type) { - // Compile structures by default - return getStructure(type, true); - } - /** * Retrieve a cached structure modifier given a packet type. * @@ -116,32 +98,19 @@ public class StructureCache { * @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 PacketType type = PacketRegistry.getPacketType(packetType); Preconditions.checkNotNull(type, "No packet type associated with " + packetType); - return getStructure(type, compile); + return getStructure(type); } /** * Retrieve a cached structure modifier for the given 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(final PacketType packetType, boolean compile) { + public static StructureModifier getStructure(final PacketType packetType) { Preconditions.checkNotNull(packetType, "type cannot be null"); StructureModifier modifier = STRUCTURE_MODIFIER_CACHE.computeIfAbsent(packetType, type -> { @@ -149,17 +118,6 @@ public class StructureCache { return new StructureModifier<>(packetClass, MinecraftReflection.getPacketClass(), true); }); - // check if we should compile the structure modifier now - if (compile && !(modifier instanceof CompiledStructureModifier) && COMPILING.add(packetType)) { - // compile now - BackgroundCompiler compiler = BackgroundCompiler.getInstance(); - if (compiler != null) { - compiler.scheduleCompilation( - modifier, - compiled -> STRUCTURE_MODIFIER_CACHE.put(packetType, compiled)); - } - } - return modifier; } diff --git a/src/main/java/com/comphenix/protocol/injector/netty/NettyByteBufAdapter.java b/src/main/java/com/comphenix/protocol/injector/netty/NettyByteBufAdapter.java index 0b4a19ec..e0d3f964 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/NettyByteBufAdapter.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/NettyByteBufAdapter.java @@ -1,25 +1,26 @@ /** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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 + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2015 dmulloy2 + *

+ * 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.netty; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.google.common.io.ByteStreams; import io.netty.buffer.AbstractByteBuf; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; - import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -33,35 +34,31 @@ import java.nio.channels.GatheringByteChannel; import java.nio.channels.ScatteringByteChannel; import java.nio.channels.WritableByteChannel; -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.google.common.io.ByteStreams; - /** * Construct a ByteBuf around an input stream and an output stream. *

- * Note that as streams usually don't support seeking, this implementation will ignore - * all indexing in the byte buffer. + * Note that as streams usually don't support seeking, this implementation will ignore all indexing in the byte buffer. + * * @author Kristian */ @SuppressWarnings("unused") public class NettyByteBufAdapter extends AbstractByteBuf { - private DataInputStream input; - private DataOutputStream output; - + + private static final int CAPACITY = Integer.MAX_VALUE; + // For modifying the reader or writer index private static FieldAccessor READER_INDEX; private static FieldAccessor WRITER_INDEX; - - private static final int CAPACITY = Integer.MAX_VALUE; - + + private final DataInputStream input; + private final DataOutputStream output; + private NettyByteBufAdapter(DataInputStream input, DataOutputStream output) { // Just pick a figure super(CAPACITY); this.input = input; this.output = output; - + // Prepare accessors try { if (READER_INDEX == null) { @@ -73,32 +70,37 @@ public class NettyByteBufAdapter extends AbstractByteBuf { } catch (Exception e) { throw new RuntimeException("Cannot initialize ByteBufAdapter.", e); } - + // "Infinite" reading/writing - if (input == null) + if (input == null) { READER_INDEX.set(this, Integer.MAX_VALUE); - if (output == null) + } + + if (output == null) { WRITER_INDEX.set(this, Integer.MAX_VALUE); + } } /** * Construct a new Minecraft packet serializer using the current byte buf adapter. + * * @param input - the input stream. * @return A packet serializer with a wrapped byte buf adapter. */ public static ByteBuf packetReader(DataInputStream input) { return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(input, null)); } - + /** * Construct a new Minecraft packet deserializer using the current byte buf adapter. + * * @param output - the output stream. * @return A packet serializer with a wrapped byte buf adapter. */ public static ByteBuf packetWriter(DataOutputStream output) { return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(null, output)); } - + @Override public int refCnt() { return 1; @@ -117,7 +119,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override protected byte _getByte(int paramInt) { try { - return input.readByte(); + return this.input.readByte(); } catch (IOException e) { throw new RuntimeException("Cannot read input.", e); } @@ -126,7 +128,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override protected short _getShort(int paramInt) { try { - return input.readShort(); + return this.input.readShort(); } catch (IOException e) { throw new RuntimeException("Cannot read input.", e); } @@ -135,7 +137,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override protected int _getUnsignedMedium(int paramInt) { try { - return input.readUnsignedShort(); + return this.input.readUnsignedShort(); } catch (IOException e) { throw new RuntimeException("Cannot read input.", e); } @@ -144,7 +146,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override protected int _getInt(int paramInt) { try { - return input.readInt(); + return this.input.readInt(); } catch (IOException e) { throw new RuntimeException("Cannot read input.", e); } @@ -153,7 +155,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override protected long _getLong(int paramInt) { try { - return input.readLong(); + return this.input.readLong(); } catch (IOException e) { throw new RuntimeException("Cannot read input.", e); } @@ -162,7 +164,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override protected void _setByte(int index, int value) { try { - output.writeByte(value); + this.output.writeByte(value); } catch (IOException e) { throw new RuntimeException("Cannot write output.", e); } @@ -171,7 +173,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override protected void _setShort(int index, int value) { try { - output.writeShort(value); + this.output.writeShort(value); } catch (IOException e) { throw new RuntimeException("Cannot write output.", e); } @@ -180,7 +182,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override protected void _setMedium(int index, int value) { try { - output.writeShort(value); + this.output.writeShort(value); } catch (IOException e) { throw new RuntimeException("Cannot write output.", e); } @@ -189,7 +191,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override protected void _setInt(int index, int value) { try { - output.writeInt(value); + this.output.writeInt(value); } catch (IOException e) { throw new RuntimeException("Cannot write output.", e); } @@ -198,7 +200,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override protected void _setLong(int index, long value) { try { - output.writeLong(value); + this.output.writeLong(value); } catch (IOException e) { throw new RuntimeException("Cannot write output.", e); } @@ -238,7 +240,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { try { for (int i = 0; i < length; i++) { - dst.setByte(dstIndex + i, input.read()); + dst.setByte(dstIndex + i, this.input.read()); } } catch (IOException e) { throw new RuntimeException("Cannot read input.", e); @@ -249,7 +251,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { try { - input.read(dst, dstIndex, length); + this.input.read(dst, dstIndex, length); } catch (IOException e) { throw new RuntimeException("Cannot read input.", e); } @@ -259,7 +261,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override public ByteBuf getBytes(int index, ByteBuffer dst) { try { - dst.put(ByteStreams.toByteArray(input)); + dst.put(ByteStreams.toByteArray(this.input)); } catch (IOException e) { throw new RuntimeException("Cannot read input.", e); } @@ -268,14 +270,14 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override public ByteBuf getBytes(int index, OutputStream dst, int length) throws IOException { - ByteStreams.copy(ByteStreams.limit(input, length), dst); + ByteStreams.copy(ByteStreams.limit(this.input, length), dst); return this; } @Override public int getBytes(int index, GatheringByteChannel out, int length) throws IOException { - byte[] data = ByteStreams.toByteArray(ByteStreams.limit(input, length)); - + byte[] data = ByteStreams.toByteArray(ByteStreams.limit(this.input, length)); + out.write(ByteBuffer.wrap(data)); return data.length; } @@ -284,9 +286,9 @@ public class NettyByteBufAdapter extends AbstractByteBuf { public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) { byte[] buffer = new byte[length]; src.getBytes(srcIndex, buffer); - + try { - output.write(buffer); + this.output.write(buffer); return this; } catch (IOException e) { throw new RuntimeException("Cannot write output.", e); @@ -296,7 +298,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { try { - output.write(src, srcIndex, length); + this.output.write(src, srcIndex, length); return this; } catch (IOException e) { throw new RuntimeException("Cannot write output.", e); @@ -306,7 +308,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override public ByteBuf setBytes(int index, ByteBuffer src) { try { - WritableByteChannel channel = Channels.newChannel(output); + WritableByteChannel channel = Channels.newChannel(this.output); channel.write(src); return this; @@ -318,15 +320,15 @@ public class NettyByteBufAdapter extends AbstractByteBuf { @Override public int setBytes(int index, InputStream in, int length) throws IOException { InputStream limit = ByteStreams.limit(in, length); - ByteStreams.copy(limit, output); + ByteStreams.copy(limit, this.output); return length - limit.available(); } @Override public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(length); - WritableByteChannel channel = Channels.newChannel(output); - + WritableByteChannel channel = Channels.newChannel(this.output); + int count = in.read(buffer); channel.write(buffer); return count; @@ -424,7 +426,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf { return 0; } - public int setBytes(int arg0, FileChannel arg1, long arg2, int arg3) throws IOException { + public int setBytes(int arg0, FileChannel arg1, long arg2, int arg3) { return 0; } diff --git a/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java b/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java index 727f2741..8ef8153a 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java @@ -19,11 +19,13 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.StreamSerializer; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import java.lang.reflect.Method; +import io.netty.util.ReferenceCountUtil; import java.util.Arrays; /** @@ -31,6 +33,7 @@ import java.util.Arrays; * * @author dmulloy2 */ +@SuppressWarnings("deprecation") // yea we need to do that :/ public class WirePacket { private final int id; @@ -38,7 +41,8 @@ public class WirePacket { /** * Constructs a new WirePacket with a given type and contents - * @param type Type of the packet + * + * @param type Type of the packet * @param bytes Contents of the packet */ public WirePacket(PacketType type, byte[] bytes) { @@ -48,7 +52,8 @@ public class WirePacket { /** * Constructs a new WirePacket with a given id and contents - * @param id ID of the packet + * + * @param id ID of the packet * @param bytes Contents of the packet */ public WirePacket(int id, byte[] bytes) { @@ -56,14 +61,9 @@ public class WirePacket { this.bytes = bytes; } - private static byte[] getBytes(ByteBuf buffer) { - byte[] array = new byte[buffer.readableBytes()]; - buffer.readBytes(array); - return array; - } - /** * Creates a WirePacket from an existing PacketContainer + * * @param packet Existing packet * @return The resulting WirePacket */ @@ -73,8 +73,7 @@ public class WirePacket { } /** - * Creates a byte array from an existing PacketContainer containing all the - * bytes from that packet + * Creates a byte array from an existing PacketContainer containing all the bytes from that packet * * @param packet Existing packet * @return the byte array @@ -86,17 +85,10 @@ public class WirePacket { ByteBuf store = PacketContainer.createPacketBuffer(); // Read the bytes once - Method write = MinecraftMethods.getPacketWriteByteBufMethod(); + MethodAccessor write = MinecraftMethods.getPacketWriteByteBufMethod(); + write.invoke(packet.getHandle(), buffer); - try { - write.invoke(packet.getHandle(), buffer); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to read packet contents.", ex); - } - - byte[] bytes = getBytes(buffer); - - buffer.release(); + byte[] bytes = StreamSerializer.getDefault().getBytesAndRelease(buffer); // Rewrite them to the packet to avoid issues with certain packets if (packet.getType() == PacketType.Play.Server.CUSTOM_PAYLOAD @@ -105,24 +97,19 @@ public class WirePacket { byte[] ret = Arrays.copyOf(bytes, bytes.length); store.writeBytes(bytes); - Method read = MinecraftMethods.getPacketReadByteBufMethod(); + MethodAccessor read = MinecraftMethods.getPacketReadByteBufMethod(); + read.invoke(packet.getHandle(), store); - try { - read.invoke(packet.getHandle(), store); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to rewrite packet contents.", ex); - } - - return ret; + bytes = ret; } - store.release(); - + ReferenceCountUtil.safeRelease(store); return bytes; } /** * Creates a WirePacket from an existing Minecraft packet + * * @param packet Existing Minecraft packet * @return The resulting WirePacket * @throws IllegalArgumentException If the packet is null or not a Minecraft packet @@ -131,57 +118,44 @@ public class WirePacket { checkNotNull(packet, "packet cannot be null!"); checkArgument(MinecraftReflection.isPacketClass(packet), "packet must be a Minecraft packet"); - PacketType type = PacketType.fromClass(packet.getClass()); - int id = type.getCurrentId(); - ByteBuf buffer = PacketContainer.createPacketBuffer(); - Method write = MinecraftMethods.getPacketWriteByteBufMethod(); - try { - write.invoke(packet, buffer); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to serialize packet contents.", ex); - } + MethodAccessor write = MinecraftMethods.getPacketWriteByteBufMethod(); + write.invoke(packet, buffer); - byte[] bytes = getBytes(buffer); - - buffer.release(); + byte[] bytes = StreamSerializer.getDefault().getBytesAndRelease(buffer); + int id = PacketType.fromClass(packet.getClass()).getCurrentId(); return new WirePacket(id, bytes); } - public static void writeVarInt(ByteBuf output, int i) { - checkNotNull(output, "output cannot be null!"); - - while ((i & -128) != 0) { - output.writeByte(i & 127 | 128); - i >>>= 7; + public static void writeVarInt(ByteBuf output, int value) { + while (true) { + if ((value & ~0x7F) == 0) { + output.writeByte(value); + break; + } else { + output.writeByte((value & 0x7F) | 0x80); + value >>>= 7; + } } - - output.writeByte(i); } public static int readVarInt(ByteBuf input) { - checkNotNull(input, "input cannot be null!"); - - int i = 0; - int j = 0; - - byte b0; - - do { - b0 = input.readByte(); - i |= (b0 & 127) << j++ * 7; - if (j > 5) { - throw new RuntimeException("VarInt too big"); + int result = 0; + for (byte j = 0; j < 5; j++) { + int nextByte = input.readByte(); + result |= (nextByte & 0x7F) << j * 7; + if ((nextByte & 0x80) != 128) { + return result; } - } while ((b0 & 128) == 128); - - return i; + } + throw new RuntimeException("VarInt is too big"); } /** * Gets this packet's ID + * * @return The ID */ public int getId() { @@ -190,6 +164,7 @@ public class WirePacket { /** * Gets this packet's contents as a byte array + * * @return The contents */ public byte[] getBytes() { @@ -198,6 +173,7 @@ public class WirePacket { /** * Writes the id of this packet to a given output + * * @param output Output to write to */ public void writeId(ByteBuf output) { @@ -206,6 +182,7 @@ public class WirePacket { /** * Writes the contents of this packet to a given output + * * @param output Output to write to */ public void writeBytes(ByteBuf output) { @@ -215,6 +192,7 @@ public class WirePacket { /** * Fully writes the ID and contents of this packet to a given output + * * @param output Output to write to */ public void writeFully(ByteBuf output) { @@ -224,6 +202,7 @@ public class WirePacket { /** * Serializes this packet into a byte buffer + * * @return The buffer */ public ByteBuf serialize() { diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java index 0c16a548..129ba9b9 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java @@ -14,7 +14,6 @@ import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; -import com.comphenix.protocol.utility.ByteBuddyFactory; import com.comphenix.protocol.utility.ByteBuddyGenerated; import com.comphenix.protocol.utility.MinecraftFields; import com.comphenix.protocol.utility.MinecraftMethods; @@ -146,7 +145,7 @@ public class NettyChannelInjector implements Injector { .typeExact(Channel.class) .banModifier(Modifier.STATIC) .build()); - this.channelField = Accessors.getFieldAccessor(channelField, true); + this.channelField = Accessors.getFieldAccessor(channelField); // hook here into the close future to be 100% sure that this injector gets closed when the channel we wrap gets closed // normally we listen to the disconnect event, but there is a very small period of time, between the login and actual @@ -192,7 +191,7 @@ public class NettyChannelInjector implements Injector { // and to be sure that the netty pipeline view we get is up-to-date if (this.wrappedChannel.eventLoop().inEventLoop()) { // ensure that we should actually inject into the channel - if (this.closed || this.wrappedChannel instanceof ByteBuddyFactory || !this.wrappedChannel.isActive()) { + if (this.closed || this.wrappedChannel instanceof ByteBuddyGenerated || !this.wrappedChannel.isActive()) { return false; } @@ -441,7 +440,7 @@ public class NettyChannelInjector implements Injector { .banModifier(Modifier.STATIC) .typeExact(int.class) .build()); - PROTOCOL_VERSION_ACCESSOR = Accessors.getFieldAccessor(ver, true); + PROTOCOL_VERSION_ACCESSOR = Accessors.getFieldAccessor(ver); } catch (IllegalArgumentException exception) { // unable to resolve that field, continue no-op PROTOCOL_VERSION_ACCESSOR = NO_OP_ACCESSOR; @@ -598,7 +597,7 @@ public class NettyChannelInjector implements Injector { .newBuilder() .typeSuperOf(MinecraftReflection.getPacketClass()) .build()); - return Accessors.getFieldAccessor(packetField, true); + return Accessors.getFieldAccessor(packetField); } catch (IllegalArgumentException exception) { // no such field found :( return NO_OP_ACCESSOR; diff --git a/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerInjector.java index 1bcc687f..9fe00997 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerInjector.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerInjector.java @@ -194,7 +194,7 @@ public class NetworkManagerInjector implements ChannelListener { if (field.getGenericType().getTypeName().contains(ChannelFuture.class.getName())) { // we can only guess if we need to override it, but it looks like we should. // we now need the old value of the field to wrap it into a new collection - FieldAccessor accessor = Accessors.getFieldAccessor(field, true); + FieldAccessor accessor = Accessors.getFieldAccessor(field); List value = (List) accessor.get(serverConnection); // mark down that we've overridden the field diff --git a/src/main/java/com/comphenix/protocol/injector/packet/MapContainer.java b/src/main/java/com/comphenix/protocol/injector/packet/MapContainer.java index 9d0732c5..23346004 100644 --- a/src/main/java/com/comphenix/protocol/injector/packet/MapContainer.java +++ b/src/main/java/com/comphenix/protocol/injector/packet/MapContainer.java @@ -1,8 +1,8 @@ package com.comphenix.protocol.injector.packet; -import java.lang.reflect.Field; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import com.comphenix.protocol.reflect.FieldUtils; import static com.google.common.base.Preconditions.checkNotNull; /** @@ -11,7 +11,7 @@ import static com.google.common.base.Preconditions.checkNotNull; */ public class MapContainer { // For detecting changes - private final Field modCountField; + private final FieldAccessor modCountField; private int lastModCount; // The object along with whether or not this is the initial run @@ -21,9 +21,10 @@ public class MapContainer { public MapContainer(Object source) { this.source = source; this.changed = false; - - Field modCountField = FieldUtils.getField(source.getClass(), "modCount", true); - this.modCountField = checkNotNull(modCountField, "Could not obtain modCount field"); + + this.modCountField = Accessors.getFieldAccessorOrNull(source.getClass(), "modCount", int.class); + checkNotNull(this.modCountField, "unable to retrieve modCount field for " + source.getClass()); + this.lastModCount = getModificationCount(); } @@ -62,10 +63,6 @@ public class MapContainer { * @return The current count */ private int getModificationCount() { - try { - return modCountField.getInt(source); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Unable to retrieve modCount.", ex); - } + return (int) modCountField.get(source); } } diff --git a/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java b/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java index c7d9051b..2236d987 100644 --- a/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java +++ b/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java @@ -112,7 +112,7 @@ public class PacketRegistry { // Iterate through the protocols for (Object protocol : protocols) { if (modifier == null) { - modifier = new StructureModifier<>(protocol.getClass().getSuperclass(), false); + modifier = new StructureModifier<>(protocol.getClass().getSuperclass()); } StructureModifier>>> maps = modifier.withTarget(protocol).withType(Map.class); diff --git a/src/main/java/com/comphenix/protocol/reflect/ClassAnalyser.java b/src/main/java/com/comphenix/protocol/reflect/ClassAnalyser.java deleted file mode 100644 index 356d0658..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/ClassAnalyser.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.comphenix.protocol.reflect; - -import java.io.IOException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; - -import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod.AsmOpcodes; -import net.bytebuddy.jar.asm.ClassReader; -import net.bytebuddy.jar.asm.ClassVisitor; -import net.bytebuddy.jar.asm.MethodVisitor; -import net.bytebuddy.jar.asm.Opcodes; -import net.bytebuddy.jar.asm.Type; - -public class ClassAnalyser { - /** - * Represents a method in ASM. - *

- * Keep in mind that this may also invoke a constructor. - * @author Kristian - */ - public static class AsmMethod { - public enum AsmOpcodes { - INVOKE_VIRTUAL, - INVOKE_SPECIAL, - INVOKE_STATIC, - INVOKE_INTERFACE, - INVOKE_DYNAMIC; - - public static AsmOpcodes fromIntOpcode(int opcode) { - switch (opcode) { - case Opcodes.INVOKEVIRTUAL: return AsmOpcodes.INVOKE_VIRTUAL; - case Opcodes.INVOKESPECIAL: return AsmOpcodes.INVOKE_SPECIAL; - case Opcodes.INVOKESTATIC: return AsmOpcodes.INVOKE_STATIC; - case Opcodes.INVOKEINTERFACE: return AsmOpcodes.INVOKE_INTERFACE; - case Opcodes.INVOKEDYNAMIC: return AsmOpcodes.INVOKE_DYNAMIC; - default: throw new IllegalArgumentException("Unknown opcode: " + opcode); - } - } - } - - private final AsmOpcodes opcode; - private final String ownerClass; - private final String methodName; - private final String signature; - - public AsmMethod(AsmOpcodes opcode, String ownerClass, String methodName, String signature) { - this.opcode = opcode; - this.ownerClass = ownerClass; - this.methodName = methodName; - this.signature = signature; - } - - public String getOwnerName() { - return ownerClass; - } - - /** - * Retrieve the opcode used to invoke this method or constructor. - * @return The opcode. - */ - public AsmOpcodes getOpcode() { - return opcode; - } - - /** - * Retrieve the associated owner class. - * @return The owner class. - * @throws ClassNotFoundException If the class was not found - */ - public Class getOwnerClass() throws ClassNotFoundException { - return AsmMethod.class.getClassLoader().loadClass(getOwnerName().replace('/', '.')); - } - - public String getMethodName() { - return methodName; - } - - public String getSignature() { - return signature; - } - } - private static final ClassAnalyser DEFAULT = new ClassAnalyser(); - - /** - * Retrieve the default instance. - * @return The default. - */ - public static ClassAnalyser getDefault() { - return DEFAULT; - } - - /** - * Retrieve every method calls in the given method. - * @param method - the method to analyse. - * @return The method calls. - * @throws IOException Cannot access the parent class. - */ - public List getMethodCalls(Method method) throws IOException { - return getMethodCalls(method.getDeclaringClass(), method); - } - - /** - * Retrieve every method calls in the given method. - * @param clazz - the parent class. - * @param method - the method to analyse. - * @return The method calls. - * @throws IOException Cannot access the parent class. - */ - private List getMethodCalls(Class clazz, Method method) throws IOException { - final ClassReader reader = new ClassReader(clazz.getCanonicalName()); - final List output = new ArrayList<>(); - - // The method we are looking for - final String methodName = method.getName(); - final String methodDescription = Type.getMethodDescriptor(method); - - reader.accept(new ClassVisitor(Opcodes.ASM5) { - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - if (methodName.equals(name) && methodDescription.equals(desc)) { - return new MethodVisitor(Opcodes.ASM5) { - @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean flag) { - output.add(new AsmMethod(AsmOpcodes.fromIntOpcode(opcode), owner, methodName, desc)); - } - }; - } - - return null; - } - }, ClassReader.EXPAND_FRAMES); - return output; - } -} diff --git a/src/main/java/com/comphenix/protocol/reflect/EquivalentConverter.java b/src/main/java/com/comphenix/protocol/reflect/EquivalentConverter.java index fd52435f..21a7ddbe 100644 --- a/src/main/java/com/comphenix/protocol/reflect/EquivalentConverter.java +++ b/src/main/java/com/comphenix/protocol/reflect/EquivalentConverter.java @@ -2,16 +2,16 @@ * 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 + * 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. + * 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 + * 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 */ @@ -19,15 +19,17 @@ package com.comphenix.protocol.reflect; /** * Interface that converts generic objects into types and back. - * - * @author Kristian + * * @param The specific type. + * @author Kristian */ public interface EquivalentConverter { + /** * Retrieve a copy of the generic type from a specific type. *

* This is usually a native net.minecraft.server type in Minecraft. + * * @param specific - the specific type we need to copy. * @return A copy of the specific type. */ @@ -37,6 +39,7 @@ public interface EquivalentConverter { * Retrieve a copy of the specific type using an instance of the generic type. *

* This is usually a wrapper type in the Bukkit API or ProtocolLib API. + * * @param generic - the generic type. * @return The new specific type. */ @@ -44,6 +47,7 @@ public interface EquivalentConverter { /** * Due to type erasure, we need to explicitly keep a reference to the specific type. + * * @return The specific type. */ Class getSpecificType(); diff --git a/src/main/java/com/comphenix/protocol/reflect/ExactReflection.java b/src/main/java/com/comphenix/protocol/reflect/ExactReflection.java index 48e7eac2..a6b11cd4 100644 --- a/src/main/java/com/comphenix/protocol/reflect/ExactReflection.java +++ b/src/main/java/com/comphenix/protocol/reflect/ExactReflection.java @@ -1,15 +1,17 @@ package com.comphenix.protocol.reflect; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; -import javax.annotation.Nonnull; - -import com.google.common.base.Preconditions; - public class ExactReflection { + + private static final Joiner COMMA_SEPERATED_JOINER = Joiner.on(", "); + // The class we're actually representing private final Class source; private final boolean forceAccess; @@ -18,126 +20,194 @@ public class ExactReflection { this.source = Preconditions.checkNotNull(source, "source class cannot be NULL"); this.forceAccess = forceAccess; } - + /** * Retrieves an exact reflection instance from a given class. - * @param source - the class we'll use. - * @return A fuzzy reflection instance. - */ - public static ExactReflection fromClass(Class source) { - return fromClass(source, false); - } - - /** - * Retrieves an exact reflection instance from a given class. - * @param source - the class we'll use. - * @param forceAccess - whether or not to override scope restrictions. + * + * @param source - the class we'll use. + * @param forceAccess - whether to also search for members which are out of the allowed scope. * @return A fuzzy reflection instance. */ public static ExactReflection fromClass(Class source, boolean forceAccess) { return new ExactReflection(source, forceAccess); } - + /** * Retrieves an exact reflection instance from an object. - * @param reference - the object we'll use. - * @return A fuzzy reflection instance that uses the class of the given object. - */ - public static ExactReflection fromObject(Object reference) { - return new ExactReflection(reference.getClass(), false); - } - - /** - * Retrieves an exact reflection instance from an object. - * @param reference - the object we'll use. - * @param forceAccess - whether or not to override scope restrictions. + * + * @param reference - the object we'll use. + * @param forceAccess - whether to also search for members which are out of the allowed scope. * @return A fuzzy reflection instance that uses the class of the given object. */ public static ExactReflection fromObject(Object reference, boolean forceAccess) { return new ExactReflection(reference.getClass(), forceAccess); } - + /** - * Retrieve the first method in the class hierachy with the given name and parameters. + * Retrieve the first method in the class hierarchy with the given name and parameters. *

- * If {@link #isForceAccess()} is TRUE, we will also search for protected and private methods. - * @param methodName - the method name to find, or NULL to look for everything. - * @param parameters - the parameters. - * @return The first matched method. - * @throws IllegalArgumentException If we cannot find a method by this name. + * If {@link #isForceAccess()} is TRUE, we will also search for methods which are out of the caller scope. + * + * @param methodName - the name of the method to find, NULL to only search by using the given parameters. + * @param parameters - the parameters of the method to find. + * @return the first matching method. + * @throws IllegalArgumentException if there is no method with the given name and parameter types. */ public Method getMethod(String methodName, Class... parameters) { - return getMethod(source, methodName, parameters); + Method method = this.lookupMethod(this.source, methodName, parameters); + if (method == null) { + throw new IllegalArgumentException(String.format( + "Unable to find method %s(%s) in %s", + methodName, + COMMA_SEPERATED_JOINER.join(parameters), + this.source.getName())); + } + + return method; } - - // For recursion - private Method getMethod(Class instanceClass, String methodName, Class... parameters) { - for (Method method : instanceClass.getDeclaredMethods()) { - if ((forceAccess || Modifier.isPublic(method.getModifiers())) && - (methodName == null || method.getName().equals(methodName)) && - Arrays.equals(method.getParameterTypes(), parameters)) { - - method.setAccessible(true); - return method; - } - } - // Search in every superclass - if (instanceClass.getSuperclass() != null) - return getMethod(instanceClass.getSuperclass(), methodName, parameters); - throw new IllegalArgumentException(String.format( - "Unable to find method %s (%s) in %s.", methodName, Arrays.asList(parameters), source)); - } - + /** - * Retrieve a field in the class hierachy by the given name. + * Finds the first method in the class hierarchy with the given name and parameters. *

- * If {@link #isForceAccess()} is TRUE, we will also search for protected and private fields. - * @param fieldName - the field name. Cannot be NULL. - * @return The first matched field. + * If {@link #isForceAccess()} is TRUE, we will also search for methods which are out of the caller scope. + * + * @param methodName - the name of the method to find, NULL to only search by using the given parameters. + * @param parameters - the parameters of the method to find. + * @return the first matching method, NULL if no method matches. + */ + public Method findMethod(String methodName, Class... parameters) { + return this.lookupMethod(this.source, methodName, parameters); + } + + // For recursion + private Method lookupMethod(Class instanceClass, String methodName, Class... parameters) { + for (Method method : instanceClass.getDeclaredMethods()) { + if ((this.forceAccess || Modifier.isPublic(method.getModifiers())) + && (methodName == null || method.getName().equals(methodName)) + && Arrays.equals(method.getParameterTypes(), parameters)) { + return method; + } + } + + // Search in every superclass + if (instanceClass.getSuperclass() != null) { + return this.lookupMethod(instanceClass.getSuperclass(), methodName, parameters); + } + + return null; + } + + /** + * Retrieve a field in the class hierarchy by the given name. + *

+ * If {@link #isForceAccess()} is TRUE, we will also search for fields which are out of the caller scope. + * + * @param fieldName - the name of the field to find. + * @return the first matching field. + * @throws IllegalArgumentException if no field with the given name was found. */ public Field getField(String fieldName) { - return getField(source, fieldName); + Field field = this.lookupField(this.source, fieldName); + if (field == null) { + throw new IllegalArgumentException(String.format( + "Unable to find field with name %s in %s.", + fieldName, + this.source.getName())); + } + + return field; } - + + /** + * Finds a field in the class hierarchy by the given name. + *

+ * If {@link #isForceAccess()} is TRUE, we will also search for fields which are out of the caller scope. + * + * @param fieldName - the name of the field to find. + * @return the first matching field, null if no field matches. + */ + public Field findField(String fieldName) { + return this.lookupField(this.source, fieldName); + } + // For recursion - private Field getField(Class instanceClass, @Nonnull String fieldName) { - // Ignore access rules - for (Field field : instanceClass.getDeclaredFields()) { - if (field.getName().equals(fieldName)) { - field.setAccessible(true); - return field; - } - } - - // Recursively fild the correct field - if (instanceClass.getSuperclass() != null) - return getField(instanceClass.getSuperclass(), fieldName); - throw new IllegalArgumentException(String.format( - "Unable to find field %s in %s.", fieldName, source)); + private Field lookupField(Class instanceClass, String fieldName) { + for (Field field : instanceClass.getDeclaredFields()) { + if ((this.forceAccess || Modifier.isPublic(field.getModifiers())) && field.getName().equals(fieldName)) { + return field; + } + } + + // Recursively find the correct field + if (instanceClass.getSuperclass() != null) { + return this.lookupField(instanceClass.getSuperclass(), fieldName); + } + + return null; } - + + /** + * Retrieves the first constructor in the class hierarchy with the given parameters. + *

+ * If {@link #isForceAccess()} is TRUE, we will also search for constructors which are out of the caller scope. + * + * @param parameters - the parameters of the constructor to find. + * @return the first matching constructor. + * @throws IllegalArgumentException if no constructor with the given parameters was found. + */ + public Constructor getConstructor(Class... parameters) { + Constructor constructor = this.findConstructor(parameters); + if (constructor == null) { + throw new IllegalArgumentException(String.format( + "Unable to find constructor (%s) in %s", + COMMA_SEPERATED_JOINER.join(parameters), + this.source.getName())); + } + + return constructor; + } + + /** + * Finds the first constructor in the class hierarchy with the given parameters. + *

+ * If {@link #isForceAccess()} is TRUE, we will also search for constructors which are out of the caller scope. + * + * @param parameters - the parameters of the constructor to find. + * @return the first matching constructor, NULL if no constructor matches. + */ + public Constructor findConstructor(Class... parameters) { + try { + Constructor constructor = this.source.getDeclaredConstructor(parameters); + return this.forceAccess || Modifier.isPublic(constructor.getModifiers()) ? constructor : null; + } catch (NoSuchMethodException exception) { + return null; + } + } + /** * Retrieve an {@link ExactReflection} object where scope restrictions are ignored. + * * @return A copy of the current object. */ public ExactReflection forceAccess() { - return new ExactReflection(source, true); + return new ExactReflection(this.source, true); } - + /** - * Determine if we are overriding scope restrictions and will also find - * private, protected or package members. + * Determine if we are overriding scope restrictions and will also find private, protected or package members. + * * @return TRUE if we are, FALSE otherwise. */ public boolean isForceAccess() { - return forceAccess; + return this.forceAccess; } - - /** + + /** * Retrieve the source class we are searching. + * * @return The source. */ public Class getSource() { - return source; + return this.source; } - } +} diff --git a/src/main/java/com/comphenix/protocol/reflect/FieldAccessException.java b/src/main/java/com/comphenix/protocol/reflect/FieldAccessException.java index 48bdac97..5d6f1e27 100644 --- a/src/main/java/com/comphenix/protocol/reflect/FieldAccessException.java +++ b/src/main/java/com/comphenix/protocol/reflect/FieldAccessException.java @@ -19,7 +19,7 @@ package com.comphenix.protocol.reflect; /** * Invoked when a field is inaccessible due to security limitations, or when it simply doesn't exist. - * + * * @author Kristian */ public class FieldAccessException extends RuntimeException { @@ -32,7 +32,7 @@ public class FieldAccessException extends RuntimeException { public FieldAccessException() { super(); } - + public FieldAccessException(String message, Throwable cause) { super(message, cause); } @@ -44,7 +44,7 @@ public class FieldAccessException extends RuntimeException { public FieldAccessException(Throwable cause) { super(cause); } - + public static FieldAccessException fromFormat(String message, Object... params) { return new FieldAccessException(String.format(message, params)); } diff --git a/src/main/java/com/comphenix/protocol/reflect/FieldUtils.java b/src/main/java/com/comphenix/protocol/reflect/FieldUtils.java deleted file mode 100644 index d921da63..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/FieldUtils.java +++ /dev/null @@ -1,510 +0,0 @@ -package com.comphenix.protocol.reflect; - -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import com.comphenix.protocol.reflect.accessors.Accessors; - -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Field; -import java.lang.reflect.Member; -import java.lang.reflect.Modifier; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -/** - * Utilities for working with fields by reflection. Adapted and refactored from - * the dormant [reflect] Commons sandbox component. - *

- * The ability is provided to break the scoping restrictions coded by the - * programmer. This can allow fields to be changed that shouldn't be. This - * facility should be used with care. - * - * @author Apache Software Foundation - * @author Matt Benson - * @since 2.5 - * @version $Id: FieldUtils.java 1057009 2011-01-09 19:48:06Z niallp $ - */ -@SuppressWarnings("rawtypes") -public class FieldUtils { - - /** - * FieldUtils instances should NOT be constructed in standard programming. - *

- * This constructor is public to permit tools that require a JavaBean - * instance to operate. - */ - public FieldUtils() { - super(); - } - - /** - * Gets an accessible Field by name respecting scope. - * Superclasses/interfaces will be considered. - * - * @param cls the class to reflect, must not be null - * @param fieldName the field name to obtain - * @return the Field object - * @throws IllegalArgumentException if the class or field name is null - */ - public static Field getField(Class cls, String fieldName) { - Field field = getField(cls, fieldName, false); - MemberUtils.setAccessibleWorkaround(field); - return field; - } - - /** - * Gets an accessible Field by name breaking scope if - * requested. Superclasses/interfaces will be considered. - * - * @param cls the class to reflect, must not be null - * @param fieldName the field name to obtain - * @param forceAccess whether to break scope restrictions using the - * setAccessible method. False will - * only match public fields. - * @return the Field object - * @throws IllegalArgumentException if the class or field name is null - */ - public static Field getField(final Class cls, String fieldName, boolean forceAccess) { - if (cls == null) { - throw new IllegalArgumentException("The class must not be null"); - } - if (fieldName == null) { - throw new IllegalArgumentException("The field name must not be null"); - } - // Sun Java 1.3 has a bugged implementation of getField hence we write - // the - // code ourselves - - // getField() will return the Field object with the declaring class - // set correctly to the class that declares the field. Thus requesting - // the - // field on a subclass will return the field from the superclass. - // - // priority order for lookup: - // searchclass private/protected/package/public - // superclass protected/package/public - // private/different package blocks access to further superclasses - // implementedinterface public - - // check up the superclass hierarchy - for (Class acls = cls; acls != null; acls = acls.getSuperclass()) { - try { - Field field = acls.getDeclaredField(fieldName); - // getDeclaredField checks for non-public scopes as well - // and it returns accurate results - if (!Modifier.isPublic(field.getModifiers())) { - if (forceAccess) { - field.setAccessible(true); - } else { - continue; - } - } - return field; - } catch (NoSuchFieldException ex) { - // ignore - } - } - // check the public interface case. This must be manually searched for - // in case there is a public supersuperclass field hidden by a - // private/package - // superclass field. - Field match = null; - for (Iterator intf = getAllInterfaces(cls).iterator(); intf.hasNext();) { - try { - Field test = ((Class) intf.next()).getField(fieldName); - if (match != null) { - throw new IllegalArgumentException("Reference to field " + fieldName - + " is ambiguous relative to " + cls - + "; a matching field exists on two or more implemented interfaces."); - } - match = test; - } catch (NoSuchFieldException ex) { - // ignore - } - } - return match; - } - - /** - *

Gets a List of all interfaces implemented by the given - * class and its superclasses.

- * - *

The order is determined by looking through each interface in turn as - * declared in the source file and following its hierarchy up. Then each - * superclass is considered in the same way. Later duplicates are ignored, - * so the order is maintained.

- * - * @param cls the class to look up, may be null - * @return the List of interfaces in order, - * null if null input - */ - private static Set getAllInterfaces(Class cls) { - if (cls == null) return null; - - final Set result = new HashSet<>(); - - while (cls != null) { - final Class[] interfaces = cls.getInterfaces(); - Collections.addAll(result, interfaces); - - for (Class anInterface : interfaces) { - result.addAll(getAllInterfaces(anInterface)); - } - cls = cls.getSuperclass(); - } - return result; - } - - /** - * Read an accessible static Field. - * - * @param field to read - * @return the field value - * @throws IllegalArgumentException if the field is null or not static - * @throws IllegalAccessException if the field is not accessible - */ - public static Object readStaticField(Field field) throws IllegalAccessException { - return readStaticField(field, false); - } - - /** - * Read a static Field. - * - * @param field to read - * @param forceAccess whether to break scope restrictions using the - * setAccessible method. - * @return the field value - * @throws IllegalArgumentException if the field is null or not static - * @throws IllegalAccessException if the field is not made accessible - */ - public static Object readStaticField(Field field, boolean forceAccess) - throws IllegalAccessException { - if (field == null) { - throw new IllegalArgumentException("The field must not be null"); - } - if (!Modifier.isStatic(field.getModifiers())) { - throw new IllegalArgumentException("The field '" + field.getName() + "' is not static"); - } - return readField(field, (Object) null, forceAccess); - } - - /** - * Read the named public static field. Superclasses will be considered. - * - * @param cls the class to reflect, must not be null - * @param fieldName the field name to obtain - * @return the value of the field - * @throws IllegalArgumentException if the class or field name is null - * @throws IllegalAccessException if the field is not accessible - */ - public static Object readStaticField(Class cls, String fieldName) throws IllegalAccessException { - return readStaticField(cls, fieldName, false); - } - - /** - * Read the named static field. Superclasses will be considered. - * - * @param cls the class to reflect, must not be null - * @param fieldName the field name to obtain - * @param forceAccess whether to break scope restrictions using the - * setAccessible method. False will - * only match public fields. - * @return the Field object - * @throws IllegalArgumentException if the class or field name is null - * @throws IllegalAccessException if the field is not made accessible - */ - public static Object readStaticField(Class cls, String fieldName, boolean forceAccess) - throws IllegalAccessException { - Field field = getField(cls, fieldName, forceAccess); - if (field == null) { - throw new IllegalArgumentException("Cannot locate field " + fieldName + " on " + cls); - } - // already forced access above, don't repeat it here: - return readStaticField(field, false); - } - - /** - * Read an accessible Field. - * - * @param field the field to use - * @param target the object to call on, may be null for static fields - * @return the field value - * @throws IllegalArgumentException if the field is null - * @throws IllegalAccessException if the field is not accessible - */ - public static Object readField(Field field, Object target) throws IllegalAccessException { - return readField(field, target, false); - } - - /** - * Read a Field. - * - * @param field the field to use - * @param target the object to call on, may be null for static fields - * @param forceAccess whether to break scope restrictions using the - * setAccessible method. - * @return the field value - * @throws IllegalArgumentException if the field is null - * @throws IllegalAccessException if the field is not made accessible - */ - public static Object readField(Field field, Object target, boolean forceAccess) throws IllegalAccessException { - if (field == null) - throw new IllegalArgumentException("The field must not be null"); - - if (forceAccess && !field.isAccessible()) { - field.setAccessible(true); - } else { - MemberUtils.setAccessibleWorkaround(field); - } - return field.get(target); - } - - /** - * Read the named public field. Superclasses will be considered. - * - * @param target the object to reflect, must not be null - * @param fieldName the field name to obtain - * @return the value of the field - * @throws IllegalArgumentException if the class or field name is null - * @throws IllegalAccessException if the named field is not public - */ - public static Object readField(Object target, String fieldName) throws IllegalAccessException { - return readField(target, fieldName, false); - } - - /** - * Read the named field. Superclasses will be considered. - * - * @param target the object to reflect, must not be null - * @param fieldName the field name to obtain - * @param forceAccess whether to break scope restrictions using the - * setAccessible method. False will - * only match public fields. - * @return the field value - * @throws IllegalArgumentException if the class or field name is null - * @throws IllegalAccessException if the named field is not made accessible - */ - public static Object readField(Object target, String fieldName, boolean forceAccess) - throws IllegalAccessException { - if (target == null) { - throw new IllegalArgumentException("target object must not be null"); - } - Class cls = target.getClass(); - Field field = getField(cls, fieldName, forceAccess); - if (field == null) { - throw new IllegalArgumentException("Cannot locate field " + fieldName + " on " + cls); - } - // already forced access above, don't repeat it here: - return readField(field, target); - } - - /** - * Write a public static Field. - * - * @param field to write - * @param value to set - * @throws IllegalArgumentException if the field is null or not static - * @throws IllegalAccessException if the field is not public or is final - */ - public static void writeStaticField(Field field, Object value) throws IllegalAccessException { - writeStaticField(field, value, false); - } - - /** - * Write a static Field. - * - * @param field to write - * @param value to set - * @param forceAccess whether to break scope restrictions using the - * setAccessible method. False will - * only match public fields. - * @throws IllegalArgumentException if the field is null or not static - * @throws IllegalAccessException if the field is not made accessible or is - * final - */ - public static void writeStaticField(Field field, Object value, boolean forceAccess) - throws IllegalAccessException { - if (field == null) { - throw new IllegalArgumentException("The field must not be null"); - } - if (!Modifier.isStatic(field.getModifiers())) { - throw new IllegalArgumentException("The field '" + field.getName() + "' is not static"); - } - writeField(field, (Object) null, value, forceAccess); - } - - /** - * Write a named public static Field. Superclasses will be considered. - * - * @param cls Class on which the Field is to be found - * @param fieldName to write - * @param value to set - * @throws IllegalArgumentException if the field cannot be located or is not - * static - * @throws IllegalAccessException if the field is not public or is final - */ - public static void writeStaticField(Class cls, String fieldName, Object value) - throws IllegalAccessException { - writeStaticField(cls, fieldName, value, false); - } - - /** - * Write a named static Field. Superclasses will be considered. - * - * @param cls Class on which the Field is to be found - * @param fieldName to write - * @param value to set - * @param forceAccess whether to break scope restrictions using the - * setAccessible method. False will - * only match public fields. - * @throws IllegalArgumentException if the field cannot be located or is not - * static - * @throws IllegalAccessException if the field is not made accessible or is - * final - */ - public static void writeStaticField(Class cls, String fieldName, Object value, - boolean forceAccess) throws IllegalAccessException { - Field field = getField(cls, fieldName, forceAccess); - if (field == null) { - throw new IllegalArgumentException("Cannot locate field " + fieldName + " on " + cls); - } - // already forced access above, don't repeat it here: - writeStaticField(field, value); - } - - /** - * @deprecated Use {@link #writeStaticField(Class, String, Object, boolean)} instead. - */ - @Deprecated - public static void writeStaticFinalField(Class clazz, String fieldName, Object value, boolean forceAccess) throws Exception { - writeStaticField(clazz, fieldName, value, forceAccess); - } - - /** - * Write an accessible field. - * - * @param field to write - * @param target the object to call on, may be null for static fields - * @param value to set - * @throws IllegalArgumentException if the field is null - * @throws IllegalAccessException if the field is not accessible or is final - */ - public static void writeField(Field field, Object target, Object value) - throws IllegalAccessException { - writeField(field, target, value, false); - } - - /** - * Write a field. - * - * @param field to write - * @param target the object to call on, may be null for static fields - * @param value to set - * @param forceAccess whether to break scope restrictions using the - * setAccessible method. False will - * only match public fields. - * @throws IllegalArgumentException if the field is null - * @throws IllegalAccessException if the field is not made accessible or is - * final - */ - public static void writeField(Field field, Object target, Object value, boolean forceAccess) - throws IllegalAccessException { - if (field == null) { - throw new IllegalArgumentException("The field must not be null"); - } - - Accessors.getFieldAccessor(field, forceAccess).set(target, value); - } - - /** - * Write a public field. Superclasses will be considered. - * - * @param target the object to reflect, must not be null - * @param fieldName the field name to obtain - * @param value to set - * @throws IllegalArgumentException if target or - * fieldName is null - * @throws IllegalAccessException if the field is not accessible - */ - public static void writeField(Object target, String fieldName, Object value) - throws IllegalAccessException { - writeField(target, fieldName, value, false); - } - - /** - * Write a field. Superclasses will be considered. - * - * @param target the object to reflect, must not be null - * @param fieldName the field name to obtain - * @param value to set - * @param forceAccess whether to break scope restrictions using the - * setAccessible method. False will - * only match public fields. - * @throws IllegalArgumentException if target or - * fieldName is null - * @throws IllegalAccessException if the field is not made accessible - */ - public static void writeField(Object target, String fieldName, Object value, boolean forceAccess) - throws IllegalAccessException { - if (target == null) { - throw new IllegalArgumentException("target object must not be null"); - } - Class cls = target.getClass(); - Field field = getField(cls, fieldName, forceAccess); - if (field == null) { - throw new IllegalArgumentException("Cannot locate declared field " + cls.getName() - + "." + fieldName); - } - // already forced access above, don't repeat it here: - writeField(field, target, value); - } - - // Useful member methods - private static class MemberUtils { - - private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED - | Modifier.PRIVATE; - - public static void setAccessibleWorkaround(AccessibleObject o) { - if (o == null || o.isAccessible()) { - return; - } - Member m = (Member) o; - if (Modifier.isPublic(m.getModifiers()) - && isPackageAccess(m.getDeclaringClass().getModifiers())) { - try { - o.setAccessible(true); - } catch (SecurityException e) { // NOPMD - // ignore in favor of subsequent IllegalAccessException - } - } - } - - /** - * Returns whether a given set of modifiers implies package access. - * - * @param modifiers to test - * @return true unless package/protected/private modifier detected - */ - public static boolean isPackageAccess(int modifiers) { - return (modifiers & ACCESS_TEST) == 0; - } - } -} diff --git a/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java b/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java index 6d9a7aa2..13a70bf1 100644 --- a/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java +++ b/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java @@ -17,157 +17,174 @@ package com.comphenix.protocol.reflect; -import java.lang.reflect.*; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; +import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.google.common.base.Joiner; +import com.google.common.collect.Sets; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.regex.Pattern; -import org.apache.commons.lang.Validate; - -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; -import com.google.common.collect.Sets; - /** * Retrieves fields and methods by signature, not just name. - * + * * @author Kristian */ public class FuzzyReflection { - // The class we're actually representing - private Class source; - // Whether or not to lookup private members - private boolean forceAccess; + private static final Joiner COMMA_JOINER = Joiner.on(", "); + + // The class we're actually representing + private final Class source; + private final boolean forceAccess; public FuzzyReflection(Class source, boolean forceAccess) { this.source = source; this.forceAccess = forceAccess; } - + /** * Retrieves a fuzzy reflection instance from a given class. + * * @param source - the class we'll use. * @return A fuzzy reflection instance. */ public static FuzzyReflection fromClass(Class source) { return fromClass(source, false); } - + /** * Retrieves a fuzzy reflection instance from a given class. - * @param source - the class we'll use. - * @param forceAccess - whether or not to override scope restrictions. + * + * @param source - the class we'll use. + * @param forceAccess - whether to override scope restrictions. * @return A fuzzy reflection instance. */ public static FuzzyReflection fromClass(Class source, boolean forceAccess) { return new FuzzyReflection(source, forceAccess); } - + /** * Retrieves a fuzzy reflection instance from an object. + * * @param reference - the object we'll use. * @return A fuzzy reflection instance that uses the class of the given object. */ public static FuzzyReflection fromObject(Object reference) { return new FuzzyReflection(reference.getClass(), false); } - + /** * Retrieves a fuzzy reflection instance from an object. - * @param reference - the object we'll use. - * @param forceAccess - whether or not to override scope restrictions. + * + * @param reference - the object we'll use. + * @param forceAccess - whether to override scope restrictions. * @return A fuzzy reflection instance that uses the class of the given object. */ public static FuzzyReflection fromObject(Object reference, boolean forceAccess) { return new FuzzyReflection(reference.getClass(), forceAccess); } - + /** * Retrieve the value of the first field of the given type. - * @param Type - * @param instance - the instance to retrieve from. - * @param fieldClass - type of the field to retrieve. - * @param forceAccess - whether or not to look for private and protected fields. + * + * @param Type + * @param instance - the instance to retrieve from. + * @param fieldClass - type of the field to retrieve. + * @param forceAccess - whether to look for private and protected fields. * @return The value of that field. * @throws IllegalArgumentException If the field cannot be found. */ + @SuppressWarnings("unchecked") public static T getFieldValue(Object instance, Class fieldClass, boolean forceAccess) { - @SuppressWarnings("unchecked") - T result = (T) Accessors.getFieldAccessor(instance.getClass(), fieldClass, forceAccess).get(instance); + return (T) Accessors.getFieldAccessor(instance.getClass(), fieldClass, forceAccess).get(instance); + } + + @SafeVarargs + public static Set combineArrays(T[]... arrays) { + Set result = new LinkedHashSet<>(); + for (T[] elements : arrays) { + Collections.addAll(result, elements); + } + return result; } - + /** * Retrieves the underlying class. + * * @return The underlying class. */ public Class getSource() { - return source; + return this.source; } - + + /** + * Retrieves whether or not not to override any scope restrictions. + * + * @return TRUE if we override scope, FALSE otherwise. + */ + public boolean isForceAccess() { + return this.forceAccess; + } + /** * Retrieve the singleton instance of a class, from a method or field. + * * @return The singleton instance. * @throws IllegalStateException If the class has no singleton. */ public Object getSingleton() { - Method method = null; - Field field = null; - try { - method = getMethod( - FuzzyMethodContract.newBuilder(). - parameterCount(0). - returnDerivedOf(source). - requireModifier(Modifier.STATIC). - build() - ); - } catch (IllegalArgumentException e) { - // Try getting the field instead - // Note that this will throw an exception if not found - field = getFieldByType("instance", source); + // try a no-args method which is static and returns the same type as the target class + Method method = this.getMethod(FuzzyMethodContract.newBuilder() + .parameterCount(0) + .returnDerivedOf(this.source) + .requireModifier(Modifier.STATIC) + .build()); + return Accessors.getMethodAccessor(method).invoke(null); + } catch (IllegalArgumentException ignored) { + // that method doesn't exist... } - // Convert into unchecked exceptions - if (method != null) { - try { - method.setAccessible(true); - return method.invoke(null); - } catch (Exception e) { - throw new RuntimeException("Cannot invoke singleton method " + method, e); - } + try { + // try a field which is static and of the same type as the target class + Field field = this.getField(FuzzyFieldContract.newBuilder() + .typeDerivedOf(this.source) + .requireModifier(Modifier.STATIC) + .build()); + return Accessors.getFieldAccessor(field).get(null); + } catch (IllegalArgumentException ignored) { + // that field doesn't exist } - if (field != null) { - try { - field.setAccessible(true); - return field.get(null); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot get content of singleton field " + field, e); - } - } - // We should never get to this point - throw new IllegalStateException("Impossible."); + + // we're unable to find the field + throw new IllegalStateException("Unable to retrieve singleton instance of " + this.source); } - + /** * Retrieve the first method that matches. *

* ForceAccess must be TRUE in order for this method to access private, protected and package level method. + * * @param matcher - the matcher to use. * @return The first method that satisfies the given matcher. * @throws IllegalArgumentException If the method cannot be found. */ public Method getMethod(AbstractFuzzyMatcher matcher) { - List result = getMethodList(matcher); - + List result = this.getMethodList(matcher); if (result.size() > 0) { return result.get(0); } else { @@ -176,19 +193,21 @@ public class FuzzyReflection { } /** - * Retrieve a method that matches. If there are multiple methods that match, the first one with the preferred - * name is selected. + * Retrieve a method that matches. If there are multiple methods that match, the first one with the preferred name is + * selected. *

* ForceAccess must be TRUE in order for this method to access private, protected and package level method. - * @param matcher - the matcher to use. - * @param preferred - the preferred name. + * + * @param matcher - the matcher to use. + * @param preferred - the preferred name, null for no preference. * @return The first method that satisfies the given matcher. * @throws IllegalArgumentException If the method cannot be found. */ public Method getMethod(AbstractFuzzyMatcher matcher, String preferred) { - List result = getMethodList(matcher); + List result = this.getMethodList(matcher); - if (result.size() > 1) { + // if we got more than one result check for the preferred method name + if (result.size() > 1 && preferred != null) { for (Method method : result) { if (method.getName().equals(preferred)) { return method; @@ -203,47 +222,32 @@ public class FuzzyReflection { } } - /** - * Retrieve a list of every method that matches the given matcher. - *

- * ForceAccess must be TRUE in order for this method to access private, protected and package level methods. - * @param matcher - the matcher to apply. - * @return List of found methods. - */ - public List getMethodList(AbstractFuzzyMatcher matcher) { - final List methods = new ArrayList<>(); - - // Add all matching fields to the list - for (Method method : getMethods()) { - if (matcher.isMatch(MethodInfo.fromMethod(method), source)) { - methods.add(method); - } - } - return methods; - } - /** * Retrieves a method by looking at its name. + * * @param nameRegex - regular expression that will match method names. * @return The first method that satisfies the regular expression. * @throws IllegalArgumentException If the method cannot be found. */ public Method getMethodByName(String nameRegex) { + // compile the regex only once Pattern match = Pattern.compile(nameRegex); - - for (Method method : getMethods()) { + for (Method method : this.getMethods()) { if (match.matcher(method.getName()).matches()) { // Right - this is probably it. return method; } } - throw new IllegalArgumentException("Unable to find a method with the pattern " + - nameRegex + " in " + source.getName()); + throw new IllegalArgumentException(String.format( + "Unable to find a method in %s that matches \"%s\"", + this.source, + nameRegex)); } - + /** * Retrieves a method by looking at the parameter types only. + * * @param name - potential name of the method. Only used by the error mechanism. * @param args - parameter types of the method to find. * @return The first method that satisfies the parameter types. @@ -251,234 +255,211 @@ public class FuzzyReflection { */ public Method getMethodByParameters(String name, Class... args) { // Find the correct method to call - for (Method method : getMethods()) { + for (Method method : this.getMethods()) { if (Arrays.equals(method.getParameterTypes(), args)) { return method; } } - + // That sucks - throw new IllegalArgumentException("Unable to find " + name + " in " + source.getName()); + throw new IllegalArgumentException(String.format( + "Unable to find %s(%s) in %s", + name, + COMMA_JOINER.join(args), + this.source)); } - + /** * Retrieves a method by looking at the parameter types and return type only. - * @param name - potential name of the method. Only used by the error mechanism. + * + * @param name - potential name of the method. Only used by the error mechanism. * @param returnType - return type of the method to find. - * @param args - parameter types of the method to find. + * @param args - parameter types of the method to find. * @return The first method that satisfies the parameter types. * @throws IllegalArgumentException If the method cannot be found. */ - public Method getMethodByParameters(String name, Class returnType, Class[] args) { + public Method getMethodByReturnTypeAndParameters(String name, Class returnType, Class... args) { // Find the correct method to call - List methods = getMethodListByParameters(returnType, args); - + List methods = this.getMethodListByParameters(returnType, args); if (methods.size() > 0) { return methods.get(0); } else { // That sucks - throw new IllegalArgumentException("Unable to find " + name + " in " + source.getName()); + throw new IllegalArgumentException(String.format( + "Unable to find %s(%s): %s in %s", + name, + COMMA_JOINER.join(args), + returnType, + this.source)); } } - + /** - * Retrieves a method by looking at the parameter types and return type only. - * @param name - potential name of the method. Only used by the error mechanism. - * @param returnTypeRegex - regular expression matching the return type of the method to find. - * @param argsRegex - regular expressions of the matching parameter types. - * @return The first method that satisfies the parameter types. - * @throws IllegalArgumentException If the method cannot be found. + * Retrieve a list of every method that matches the given matcher. + *

+ * ForceAccess must be TRUE in order for this method to access private, protected and package level methods. + * + * @param matcher - the matcher to apply. + * @return List of found methods. */ - public Method getMethodByParameters(String name, String returnTypeRegex, String[] argsRegex) { - Pattern match = Pattern.compile(returnTypeRegex); - Pattern[] argMatch = new Pattern[argsRegex.length]; - - for (int i = 0; i < argsRegex.length; i++) { - argMatch[i] = Pattern.compile(argsRegex[i]); - } - - // Find the correct method to call - for (Method method : getMethods()) { - if (match.matcher(method.getReturnType().getName()).matches()) { - if (matchParameters(argMatch, method.getParameterTypes())) - return method; + public List getMethodList(AbstractFuzzyMatcher matcher) { + // finds and adds all matching methods + List methods = new ArrayList<>(); + for (Method method : this.getMethods()) { + if (matcher.isMatch(MethodInfo.fromMethod(method), this.source)) { + methods.add(method); } } - - // That sucks - throw new IllegalArgumentException("Unable to find " + name + " in " + source.getName()); + + return methods; } - - /** - * Invoke a method by return type and parameters alone. - *

- * The parameters must be non-null for this to work. - * @param target - the instance. - * @param name - the name of the method - for debugging. - * @param returnType - the expected return type. - * @param parameters - the parameters. - * @return The return value, or NULL. - */ - public Object invokeMethod(Object target, String name, Class returnType, Object... parameters) { - Class[] types = new Class[parameters.length]; - - for (int i = 0; i < types.length; i++) { - types[i] = parameters[i].getClass(); - } - return Accessors.getMethodAccessor(getMethodByParameters(name, returnType, types)). - invoke(target, parameters); - } - - private boolean matchParameters(Pattern[] parameterMatchers, Class[] argTypes) { - if (parameterMatchers.length != argTypes.length) - throw new IllegalArgumentException("Arrays must have the same cardinality."); - - // Check types against the regular expressions - for (int i = 0; i < argTypes.length; i++) { - if (!parameterMatchers[i].matcher(argTypes[i].getName()).matches()) - return false; - } - - return true; - } - + /** * Retrieves every method that has the given parameter types and return type. + * * @param returnType - return type of the method to find. - * @param args - parameter types of the method to find. + * @param args - parameter types of the method to find. * @return Every method that satisfies the given constraints. */ - public List getMethodListByParameters(Class returnType, Class[] args) { - final List methods = new ArrayList<>(); - + public List getMethodListByParameters(Class returnType, Class... args) { + List methods = new ArrayList<>(); // Find the correct method to call - for (Method method : getMethods()) { + for (Method method : this.getMethods()) { if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) { methods.add(method); } } + return methods; } - - /** - * Retrieves a field by name. - * @param nameRegex - regular expression that will match a field name. - * @return The first field to match the given expression. - * @throws IllegalArgumentException If the field cannot be found. - */ - public Field getFieldByName(String nameRegex) { - Pattern match = Pattern.compile(nameRegex); - - for (Field field : getFields()) { - if (match.matcher(field.getName()).matches()) { - // Right - this is probably it. - return field; - } - } - - // Looks like we're outdated. Too bad. - throw new IllegalArgumentException("Unable to find a field with the pattern " + - nameRegex + " in " + source.getName()); - } - - /** - * Retrieves the first field with a type equal to or more specific to the given type. - * @param name - name the field probably is given. This will only be used in the error message. - * @param type - type of the field to find. - * @return The first field with a type that is an instance of the given type. - */ - public Field getFieldByType(String name, Class type) { - final List fields = getFieldListByType(type); - - if (fields.size() > 0) { - return fields.get(0); - } else { - // Looks like we're outdated. Too bad. - throw new IllegalArgumentException(String.format("Unable to find a field %s with the type %s in %s", - name, type.getName(), source.getName()) - ); - } - } - - /** - * Retrieves every field with a type equal to or more specific to the given type. - * @param type - type of the fields to find. - * @return Every field with a type that is an instance of the given type. - */ - public List getFieldListByType(Class type) { - final List fields = new ArrayList<>(); - - // Field with a compatible type - for (Field field : getFields()) { - // A assignable from B -> B instanceOf A - if (type.isAssignableFrom(field.getType())) { - fields.add(field); - } - } - - return fields; - } - /** - * Retrieves a field with a given type and parameters. This is most useful - * when dealing with Collections. - * - * @param fieldType Type of the field - * @param params Variable length array of type parameters - * @return The field - * - * @throws IllegalArgumentException If the field cannot be found - */ - public Field getParameterizedField(Class fieldType, Class... params) { - for (Field field : getFields()) { - if (field.getType().equals(fieldType)) { - Type type = field.getGenericType(); - if (type instanceof ParameterizedType) { - if (Arrays.equals(((ParameterizedType) type).getActualTypeArguments(), params)) - return field; - } - } - } - - throw new IllegalArgumentException("Unable to find a field with type " + fieldType + " and params " + Arrays.toString(params)); - } - /** * Retrieve the first field that matches. *

* ForceAccess must be TRUE in order for this method to access private, protected and package level fields. + * * @param matcher - the matcher to use. * @return The first method that satisfies the given matcher. * @throws IllegalArgumentException If the method cannot be found. */ public Field getField(AbstractFuzzyMatcher matcher) { - List result = getFieldList(matcher); - - if (result.size() > 0) + List result = this.getFieldList(matcher); + if (result.size() > 0) { return result.get(0); - else + } else { throw new IllegalArgumentException("Unable to find a field that matches " + matcher); + } } - + + /** + * Retrieves a field by name. + * + * @param nameRegex - regular expression that will match a field name. + * @return The first field to match the given expression. + * @throws IllegalArgumentException If the field cannot be found. + */ + public Field getFieldByName(String nameRegex) { + // compile the pattern only once + Pattern match = Pattern.compile(nameRegex); + for (Field field : this.getFields()) { + if (match.matcher(field.getName()).matches()) { + return field; + } + } + + // Looks like we're outdated. Too bad. + throw new IllegalArgumentException(String.format( + "Unable to find a field with a name matching \"%s\" in %s", + nameRegex, + this.source)); + } + + /** + * Retrieves the first field with a type equal to or more specific to the given type. + * + * @param name - name the field probably is given. This will only be used in the error message. + * @param type - type of the field to find. + * @return The first field with a type that is an instance of the given type. + */ + public Field getFieldByType(String name, Class type) { + List fields = this.getFieldListByType(type); + if (fields.size() > 0) { + return fields.get(0); + } else { + // Looks like we're outdated. Too bad. + throw new IllegalArgumentException(String.format( + "Unable to find a field \"%s\" with the type %s in %s", + name, + type, + this.source)); + } + } + + /** + * Retrieves every field with a type equal to or more specific to the given type. + * + * @param type - type of the fields to find. + * @return Every field with a type that is an instance of the given type. + */ + public List getFieldListByType(Class type) { + // Field with a compatible type + List fields = new ArrayList<>(); + for (Field field : this.getFields()) { + if (type.isAssignableFrom(field.getType())) { + fields.add(field); + } + } + + return fields; + } + + /** + * Retrieves a field with a given type and parameters. This is most useful when dealing with Collections. + * + * @param fieldType Type of the field + * @param params Variable length array of type parameters + * @return The field + * @throws IllegalArgumentException If the field cannot be found + */ + public Field getParameterizedField(Class fieldType, Class... params) { + for (Field field : this.getFields()) { + if (field.getType().equals(fieldType)) { + Type type = field.getGenericType(); + if (type instanceof ParameterizedType) { + if (Arrays.equals(((ParameterizedType) type).getActualTypeArguments(), params)) { + return field; + } + } + } + } + + throw new IllegalArgumentException(String.format( + "Unable to find a field of type %s<%s> in %s", + fieldType, + COMMA_JOINER.join(params), + this.source)); + } + /** * Retrieve a list of every field that matches the given matcher. *

* ForceAccess must be TRUE in order for this method to access private, protected and package level fields. + * * @param matcher - the matcher to apply. * @return List of found fields. */ public List getFieldList(AbstractFuzzyMatcher matcher) { - final List fields = new ArrayList<>(); - // Add all matching fields to the list - for (Field field : getFields()) { - if (matcher.isMatch(field, source)) { + List fields = new ArrayList<>(); + for (Field field : this.getFields()) { + if (matcher.isMatch(field, this.source)) { fields.add(field); } } + return fields; } - + /** * Retrieves a field by type. *

@@ -487,199 +468,128 @@ public class FuzzyReflection { *

  • java.util.List
  • *
  • net.comphenix.xp.ExperienceMod
  • * + * * @param typeRegex - regular expression that will match the field type. * @return The first field with a type that matches the given regular expression. * @throws IllegalArgumentException If the field cannot be found. */ public Field getFieldByType(String typeRegex) { - Pattern match = Pattern.compile(typeRegex); - + // Like above, only here we test the field type - for (Field field : getFields()) { + for (Field field : this.getFields()) { String name = field.getType().getName(); - if (match.matcher(name).matches()) { return field; } } - + // Looks like we're outdated. Too bad. - throw new IllegalArgumentException("Unable to find a field with the type " + - typeRegex + " in " + source.getName()); + throw new IllegalArgumentException(String.format( + "Unable to find a field with a type that matches \"%s\" in %s", + typeRegex, + this.source)); } - - /** - * Retrieves a field by type. - *

    - * Note that the type is matched using the full canonical representation, i.e.: - *

      - *
    • java.util.List
    • - *
    • net.comphenix.xp.ExperienceMod
    • - *
    - * @param typeRegex - regular expression that will match the field type. - * @param ignored - types to ignore. - * @return The first field with a type that matches the given regular expression. - * @throws IllegalArgumentException If the field cannot be found. - */ - @SuppressWarnings("rawtypes") - public Field getFieldByType(String typeRegex, Set ignored) { - - Pattern match = Pattern.compile(typeRegex); - - // Like above, only here we test the field type - for (Field field : getFields()) { - Class type = field.getType(); - - if (!ignored.contains(type) && match.matcher(type.getName()).matches()) { - return field; - } - } - - // Looks like we're outdated. Too bad. - throw new IllegalArgumentException("Unable to find a field with the type " + - typeRegex + " in " + source.getName()); - } - + /** * Retrieve the first constructor that matches. *

    * ForceAccess must be TRUE in order for this method to access private, protected and package level constructors. + * * @param matcher - the matcher to use. * @return The first constructor that satisfies the given matcher. * @throws IllegalArgumentException If the constructor cannot be found. */ public Constructor getConstructor(AbstractFuzzyMatcher matcher) { - final List> result = getConstructorList(matcher); - - if (result.size() > 0) + List> result = this.getConstructorList(matcher); + if (result.size() > 0) { return result.get(0); - else + } else { throw new IllegalArgumentException("Unable to find a method that matches " + matcher); - } - - /** - * Retrieve every method as a map over names. - *

    - * Note that overloaded methods will only occur once in the resulting map. - * @param methods - every method. - * @return A map over every given method. - */ - public Map getMappedMethods(List methods) { - final Map map = new HashMap<>(); - - for (Method method : methods) { - map.put(method.getName(), method); } - return map; } - + /** * Retrieve a list of every constructor that matches the given matcher. *

    * ForceAccess must be TRUE in order for this method to access private, protected and package level constructors. + * * @param matcher - the matcher to apply. * @return List of found constructors. */ public List> getConstructorList(AbstractFuzzyMatcher matcher) { - final List> constructors = new ArrayList<>(); - - // Add all matching fields to the list - for (Constructor constructor : getConstructors()) { - if (matcher.isMatch(MethodInfo.fromConstructor(constructor), source)) { + // Add all matching constructors to the list + List> constructors = new ArrayList<>(); + for (Constructor constructor : this.getConstructors()) { + if (matcher.isMatch(MethodInfo.fromConstructor(constructor), this.source)) { constructors.add(constructor); } } + return constructors; } - + /** - * Retrieves all private and public fields in declared order (after JDK 1.5). + * Retrieves all private and public fields in declared order. *

    * Private, protected and package fields are ignored if forceAccess is FALSE. + * * @return Every field. */ public Set getFields() { - Validate.notNull(source, "source cannot be null!"); - - // We will only consider private fields in the declared class - if (forceAccess) - return setUnion(source.getDeclaredFields(), source.getFields()); - else - return setUnion(source.getFields()); + if (this.forceAccess) { + return combineArrays(this.source.getDeclaredFields(), this.source.getFields()); + } else { + return combineArrays(this.source.getFields()); + } } - + /** * Retrieves all private and public fields, up until a certain superclass. + * * @param excludeClass - the class (and its superclasses) to exclude from the search. * @return Every such declared field. */ public Set getDeclaredFields(Class excludeClass) { - if (forceAccess) { - Class current = source; + // we only need to do this if we include inherited fields + if (this.forceAccess) { + Class current = this.source; Set fields = Sets.newLinkedHashSet(); - + while (current != null && current != excludeClass) { fields.addAll(Arrays.asList(current.getDeclaredFields())); current = current.getSuperclass(); } + return fields; } - return getFields(); + + return this.getFields(); } - + /** * Retrieves all private and public methods in declared order (after JDK 1.5). *

    * Private, protected and package methods are ignored if forceAccess is FALSE. + * * @return Every method. */ public Set getMethods() { - // We will only consider private methods in the declared class - if (forceAccess) - return setUnion(source.getDeclaredMethods(), source.getMethods()); - else - return setUnion(source.getMethods()); + if (this.forceAccess) { + return combineArrays(this.source.getDeclaredMethods(), this.source.getMethods()); + } else { + return combineArrays(this.source.getMethods()); + } } - + /** * Retrieves all private and public constructors in declared order (after JDK 1.5). *

    * Private, protected and package constructors are ignored if forceAccess is FALSE. + * * @return Every constructor. */ public Set> getConstructors() { - if (forceAccess) - return setUnion(source.getDeclaredConstructors()); - else - return setUnion(source.getConstructors()); - } - - // Prevent duplicate fields - - @SafeVarargs - private static Set setUnion(T[]... array) { - final Set result = new LinkedHashSet<>(); - - for (T[] elements : array) { - Collections.addAll(result, elements); - } - return result; - } - - /** - * Retrieves whether or not not to override any scope restrictions. - * @return TRUE if we override scope, FALSE otherwise. - */ - public boolean isForceAccess() { - return forceAccess; - } - - /** - * Sets whether or not not to override any scope restrictions. - * @param forceAccess - TRUE if we override scope, FALSE otherwise. - */ - public void setForceAccess(boolean forceAccess) { - this.forceAccess = forceAccess; + return combineArrays(this.forceAccess ? this.source.getDeclaredConstructors() : this.source.getConstructors()); } } diff --git a/src/main/java/com/comphenix/protocol/reflect/IntEnum.java b/src/main/java/com/comphenix/protocol/reflect/IntEnum.java deleted file mode 100644 index 22ac99ec..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/IntEnum.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.reflect; - -import java.lang.reflect.Field; -import java.util.HashSet; -import java.util.Set; - -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBiMap; - -/** - * Represents a traditional int field enum. - * - * @author Kristian - */ -public class IntEnum { - - // Used to convert between IDs and names - protected BiMap members = HashBiMap.create(); - - /** - * Registers every declared integer field. - */ - public IntEnum() { - registerAll(); - } - - /** - * Registers every public int field as a member. - */ - protected void registerAll() { - try { - // Register every int field - for (Field entry : this.getClass().getFields()) { - if (entry.getType().equals(int.class)) { - registerMember(entry.getInt(this), entry.getName()); - } - } - - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - - /** - * Registers a member. - * @param id - id of member. - * @param name - name of member. - */ - protected void registerMember(int id, String name) { - members.put(id, name); - } - - /** - * Determines whether or not the given member exists. - * @param id - the ID of the member to find. - * @return TRUE if a member with the given ID exists, FALSE otherwise. - */ - public boolean hasMember(int id) { - return members.containsKey(id); - } - - /** - * Retrieve the ID of the member with the given name. - * @param name - name of member to retrieve. - * @return ID of the member, or NULL if not found. - */ - public Integer valueOf(String name) { - return members.inverse().get(name); - } - - /** - * Retrieve the name of the member with the given id. - * @param id - id of the member to retrieve. - * @return Declared name of the member, or NULL if not found. - */ - public String getDeclaredName(Integer id) { - return members.get(id); - } - - /** - * Retrieve the ID of every registered member. - * @return Enumeration of every value. - */ - public Set values() { - return new HashSet(members.keySet()); - } -} diff --git a/src/main/java/com/comphenix/protocol/reflect/MethodInfo.java b/src/main/java/com/comphenix/protocol/reflect/MethodInfo.java index c8e3bbdb..c591a05c 100644 --- a/src/main/java/com/comphenix/protocol/reflect/MethodInfo.java +++ b/src/main/java/com/comphenix/protocol/reflect/MethodInfo.java @@ -1,94 +1,111 @@ package com.comphenix.protocol.reflect; +import com.google.common.collect.Lists; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.GenericDeclaration; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.TypeVariable; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; /** * Represents a method or a constructor. - * + * * @author Kristian */ public abstract class MethodInfo implements GenericDeclaration, Member { + /** * Wraps a method as a MethodInfo object. + * * @param method - the method to wrap. * @return The wrapped method. */ public static MethodInfo fromMethod(final Method method) { return new MethodInfo() { - // @Override + @Override public T getAnnotation(Class annotationClass) { return method.getAnnotation(annotationClass); } - // @Override + + @Override public Annotation[] getAnnotations() { return method.getAnnotations(); } - // @Override + + @Override public Annotation[] getDeclaredAnnotations() { return method.getDeclaredAnnotations(); } + @Override public String getName() { return method.getName(); } + @Override public Class[] getParameterTypes() { return method.getParameterTypes(); } + @Override public Class getDeclaringClass() { return method.getDeclaringClass(); } + @Override public Class getReturnType() { return method.getReturnType(); } + @Override public int getModifiers() { return method.getModifiers(); } + @Override public Class[] getExceptionTypes() { return method.getExceptionTypes(); } + @Override public TypeVariable[] getTypeParameters() { return method.getTypeParameters(); } + @Override public String toGenericString() { return method.toGenericString(); } + @Override public String toString() { return method.toString(); } + @Override public boolean isSynthetic() { return method.isSynthetic(); } + @Override public int hashCode() { return method.hashCode(); } + @Override public boolean isConstructor() { return false; } }; } - + /** * Construct a list of method infos from a given array of methods. + * * @param methods - array of methods. * @return Method info list. */ @@ -98,110 +115,132 @@ public abstract class MethodInfo implements GenericDeclaration, Member { /** * Construct a list of method infos from a given collection of methods. + * * @param methods - list of methods. * @return Method info list. */ public static List fromMethods(Collection methods) { - final List list = new ArrayList<>(); + List infos = Lists.newArrayList(); + for (Method method : methods) { + infos.add(fromMethod(method)); + } - for (Method method : methods) list.add(fromMethod(method)); - return list; + return infos; } - + /** * Wraps a constructor as a method information object. + * * @param constructor - the constructor to wrap. * @return A wrapped constructor. */ public static MethodInfo fromConstructor(final Constructor constructor) { return new MethodInfo() { - // @Override + @Override public T getAnnotation(Class annotationClass) { return constructor.getAnnotation(annotationClass); } - // @Override + + @Override public Annotation[] getAnnotations() { return constructor.getAnnotations(); } - // @Override + + @Override public Annotation[] getDeclaredAnnotations() { return constructor.getDeclaredAnnotations(); } + @Override public String getName() { return constructor.getName(); } + @Override public Class[] getParameterTypes() { return constructor.getParameterTypes(); } + @Override public Class getDeclaringClass() { return constructor.getDeclaringClass(); } + @Override public Class getReturnType() { return Void.class; } + @Override public int getModifiers() { return constructor.getModifiers(); } + @Override public Class[] getExceptionTypes() { return constructor.getExceptionTypes(); } + @Override public TypeVariable[] getTypeParameters() { return constructor.getTypeParameters(); } + @Override public String toGenericString() { return constructor.toGenericString(); } + @Override public String toString() { return constructor.toString(); } + @Override public boolean isSynthetic() { return constructor.isSynthetic(); } + @Override public int hashCode() { return constructor.hashCode(); } + @Override public boolean isConstructor() { return true; } }; } - + /** * Construct a list of method infos from a given array of constructors. + * * @param constructors - array of constructors. * @return Method info list. */ public static Collection fromConstructors(Constructor[] constructors) { return fromConstructors(Arrays.asList(constructors)); } - + /** * Construct a list of method infos from a given collection of constructors. + * * @param constructors - list of constructors. * @return Method info list. */ public static List fromConstructors(Collection> constructors) { - final List infos = new ArrayList<>(); - - for (Constructor constructor : constructors) + List infos = Lists.newArrayList(); + + for (Constructor constructor : constructors) { infos.add(fromConstructor(constructor)); + } return infos; } - + /** * Returns a string describing this method or constructor + * * @return A string representation of the object. * @see Method#toString() * @see Constructor#toString() @@ -213,15 +252,17 @@ public abstract class MethodInfo implements GenericDeclaration, Member { /** * Returns a string describing this method or constructor, including type parameters. + * * @return A string describing this Method, include type parameters * @see Method#toGenericString() * @see Constructor#toGenericString() */ public abstract String toGenericString(); - + /** * Returns an array of Class objects that represent the types of the exceptions declared to be thrown by the * underlying method or constructor represented by this MethodInfo object. + * * @return The exception types declared as being thrown by the method or constructor this object represents. * @see Method#getExceptionTypes() * @see Constructor#getExceptionTypes() @@ -229,26 +270,29 @@ public abstract class MethodInfo implements GenericDeclaration, Member { public abstract Class[] getExceptionTypes(); /** - * Returns a Class object that represents the formal return type of the method or constructor - * represented by this MethodInfo object. + * Returns a Class object that represents the formal return type of the method or constructor represented by this + * MethodInfo object. *

    * This is always {@link Void} for constructors. + * * @return The return value, or Void if a constructor. * @see Method#getReturnType() */ public abstract Class getReturnType(); /** - * Returns an array of Class objects that represent the formal parameter types, in declaration order, - * of the method or constructor represented by this MethodInfo object. + * Returns an array of Class objects that represent the formal parameter types, in declaration order, of the method or + * constructor represented by this MethodInfo object. + * * @return The parameter types for the method or constructor this object represents. * @see Method#getParameterTypes() * @see Constructor#getParameterTypes() */ public abstract Class[] getParameterTypes(); - + /** * Determine if this is a constructor or not. + * * @return TRUE if this represents a constructor, FALSE otherwise. */ public abstract boolean isConstructor(); diff --git a/src/main/java/com/comphenix/protocol/reflect/MethodUtils.java b/src/main/java/com/comphenix/protocol/reflect/MethodUtils.java deleted file mode 100644 index 24a44de1..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/MethodUtils.java +++ /dev/null @@ -1,1327 +0,0 @@ -package com.comphenix.protocol.reflect; - -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Collections; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.logging.Logger; - -import org.bukkit.Bukkit; - -/** - *

    Utility reflection methods focussed on methods in general rather than properties in particular.

    - * - *

    Known Limitations

    - *

    Accessing Public Methods In A Default Access Superclass

    - *

    There is an issue when invoking public methods contained in a default access superclass. - * Reflection locates these methods fine and correctly assigns them as public. - * However, an IllegalAccessException is thrown if the method is invoked.

    - * - *

    MethodUtils contains a workaround for this situation. - * It will attempt to call setAccessible on this method. - * If this call succeeds, then the method can be invoked as normal. - * This call will only succeed when the application has sufficient security privilages. - * If this call fails then a warning will be logged and the method may fail.

    - * - * @author Craig R. McClanahan - * @author Ralph Schaer - * @author Chris Audley - * @author Rey François - * @author Gregor Raýman - * @author Jan Sorensen - * @author Robert Burrell Donkin - */ - -@SuppressWarnings("rawtypes") -public class MethodUtils { - - // --------------------------------------------------------- Private Methods - - /** - * Only log warning about accessibility work around once. - *

    - * Note that this is broken when this class is deployed via a shared - * classloader in a container, as the warning message will be emitted - * only once, not once per webapp. However making the warning appear - * once per webapp means having a map keyed by context classloader - * which introduces nasty memory-leak problems. As this warning is - * really optional we can ignore this problem; only one of the webapps - * will get the warning in its logs but that should be good enough. - */ - private static boolean loggedAccessibleWarning = false; - - /** - * Indicates whether methods should be cached for improved performance. - *

    - * Note that when this class is deployed via a shared classloader in - * a container, this will affect all webapps. However making this - * configurable per webapp would mean having a map keyed by context classloader - * which may introduce memory-leak problems. - */ - private static boolean CACHE_METHODS = true; - - /** An empty class array */ - private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0]; - /** An empty object array */ - private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; - - /** - * Stores a cache of MethodDescriptor -> Method in a WeakHashMap. - *

    - * The keys into this map only ever exist as temporary variables within - * methods of this class, and are never exposed to users of this class. - * This means that the WeakHashMap is used only as a mechanism for - * limiting the size of the cache, ie a way to tell the garbage collector - * that the contents of the cache can be completely garbage-collected - * whenever it needs the memory. Whether this is a good approach to - * this problem is doubtful; something like the commons-collections - * LRUMap may be more appropriate (though of course selecting an - * appropriate size is an issue). - *

    - * This static variable is safe even when this code is deployed via a - * shared classloader because it is keyed via a MethodDescriptor object - * which has a Class as one of its members and that member is used in - * the MethodDescriptor.equals method. So two components that load the same - * class via different classloaders will generate non-equal MethodDescriptor - * objects and hence end up with different entries in the map. - */ - @SuppressWarnings("unchecked") - private static final Map cache = Collections.synchronizedMap(new WeakHashMap()); - - // --------------------------------------------------------- Public Methods - - /** - * Set whether methods should be cached for greater performance or not, - * default is true. - * - * @param cacheMethods true if methods should be - * cached for greater performance, otherwise false - * @since 1.8.0 - */ - public static synchronized void setCacheMethods(boolean cacheMethods) { - CACHE_METHODS = cacheMethods; - if (!CACHE_METHODS) { - clearCache(); - } - } - - /** - * Clear the method cache. - * @return the number of cached methods cleared - * @since 1.8.0 - */ - public static synchronized int clearCache() { - int size = cache.size(); - cache.clear(); - return size; - } - - /** - *

    Invoke a named method whose parameter type matches the object type.

    - * - *

    The behaviour of this method is less deterministic - * than invokeExactMethod(). - * It loops through all methods with names that match - * and then executes the first it finds with compatable parameters.

    - * - *

    This method supports calls to methods taking primitive parameters - * via passing in wrapping classes. So, for example, a Boolean class - * would match a boolean primitive.

    - * - *

    This is a convenient wrapper for - * {@link #invokeMethod(Object object,String methodName,Object [] args)}. - *

    - * - * @param object invoke method on this object - * @param methodName get method with this name - * @param arg use this argument - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - */ - public static Object invokeMethod( - Object object, - String methodName, - Object arg) - throws - NoSuchMethodException, - IllegalAccessException, - InvocationTargetException { - - Object[] args = {arg}; - return invokeMethod(object, methodName, args); - - } - - - /** - *

    Invoke a named method whose parameter type matches the object type.

    - * - *

    The behaviour of this method is less deterministic - * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. - * It loops through all methods with names that match - * and then executes the first it finds with compatable parameters.

    - * - *

    This method supports calls to methods taking primitive parameters - * via passing in wrapping classes. So, for example, a Boolean class - * would match a boolean primitive.

    - * - *

    This is a convenient wrapper for - * {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. - *

    - * - * @param object invoke method on this object - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - */ - public static Object invokeMethod( - Object object, - String methodName, - Object[] args) - throws - NoSuchMethodException, - IllegalAccessException, - InvocationTargetException { - - if (args == null) { - args = EMPTY_OBJECT_ARRAY; - } - int arguments = args.length; - Class[] parameterTypes = new Class[arguments]; - for (int i = 0; i < arguments; i++) { - parameterTypes[i] = args[i].getClass(); - } - return invokeMethod(object, methodName, args, parameterTypes); - - } - - - /** - *

    Invoke a named method whose parameter type matches the object type.

    - * - *

    The behaviour of this method is less deterministic - * than {@link - * #invokeExactMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. - * It loops through all methods with names that match - * and then executes the first it finds with compatable parameters.

    - * - *

    This method supports calls to methods taking primitive parameters - * via passing in wrapping classes. So, for example, a Boolean class - * would match a boolean primitive.

    - * - * - * @param object invoke method on this object - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @param parameterTypes match these parameters - treat null as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - */ - public static Object invokeMethod( - Object object, - String methodName, - Object[] args, - Class[] parameterTypes) - throws - NoSuchMethodException, - IllegalAccessException, - InvocationTargetException { - - if (parameterTypes == null) { - parameterTypes = EMPTY_CLASS_PARAMETERS; - } - if (args == null) { - args = EMPTY_OBJECT_ARRAY; - } - - Method method = getMatchingAccessibleMethod( - object.getClass(), - methodName, - parameterTypes); - if (method == null) { - throw new NoSuchMethodException("No such accessible method: " + - methodName + "() on object: " + object.getClass().getName()); - } - return method.invoke(object, args); - } - - - /** - *

    Invoke a method whose parameter type matches exactly the object - * type.

    - * - *

    This is a convenient wrapper for - * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. - *

    - * - * @param object invoke method on this object - * @param methodName get method with this name - * @param arg use this argument - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - */ - public static Object invokeExactMethod( - Object object, - String methodName, - Object arg) - throws - NoSuchMethodException, - IllegalAccessException, - InvocationTargetException { - - Object[] args = {arg}; - return invokeExactMethod(object, methodName, args); - - } - - - /** - *

    Invoke a method whose parameter types match exactly the object - * types.

    - * - *

    This uses reflection to invoke the method obtained from a call to - * getAccessibleMethod().

    - * - * @param object invoke method on this object - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - */ - public static Object invokeExactMethod( - Object object, - String methodName, - Object[] args) - throws - NoSuchMethodException, - IllegalAccessException, - InvocationTargetException { - if (args == null) { - args = EMPTY_OBJECT_ARRAY; - } - int arguments = args.length; - Class[] parameterTypes = new Class[arguments]; - for (int i = 0; i < arguments; i++) { - parameterTypes[i] = args[i].getClass(); - } - return invokeExactMethod(object, methodName, args, parameterTypes); - - } - - - /** - *

    Invoke a method whose parameter types match exactly the parameter - * types given.

    - * - *

    This uses reflection to invoke the method obtained from a call to - * getAccessibleMethod().

    - * - * @param object invoke method on this object - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @param parameterTypes match these parameters - treat null as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - */ - public static Object invokeExactMethod( - Object object, - String methodName, - Object[] args, - Class[] parameterTypes) - throws - NoSuchMethodException, - IllegalAccessException, - InvocationTargetException { - - if (args == null) { - args = EMPTY_OBJECT_ARRAY; - } - - if (parameterTypes == null) { - parameterTypes = EMPTY_CLASS_PARAMETERS; - } - - Method method = getAccessibleMethod( - object.getClass(), - methodName, - parameterTypes); - if (method == null) { - throw new NoSuchMethodException("No such accessible method: " + - methodName + "() on object: " + object.getClass().getName()); - } - return method.invoke(object, args); - - } - - /** - *

    Invoke a static method whose parameter types match exactly the parameter - * types given.

    - * - *

    This uses reflection to invoke the method obtained from a call to - * {@link #getAccessibleMethod(Class, String, Class[])}.

    - * - * @param objectClass invoke static method on this class - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @param parameterTypes match these parameters - treat null as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - * @since 1.8.0 - */ - public static Object invokeExactStaticMethod( - Class objectClass, - String methodName, - Object[] args, - Class[] parameterTypes) - throws - NoSuchMethodException, - IllegalAccessException, - InvocationTargetException { - - if (args == null) { - args = EMPTY_OBJECT_ARRAY; - } - - if (parameterTypes == null) { - parameterTypes = EMPTY_CLASS_PARAMETERS; - } - - Method method = getAccessibleMethod( - objectClass, - methodName, - parameterTypes); - if (method == null) { - throw new NoSuchMethodException("No such accessible method: " + - methodName + "() on class: " + objectClass.getName()); - } - return method.invoke(null, args); - - } - - /** - *

    Invoke a named static method whose parameter type matches the object type.

    - * - *

    The behaviour of this method is less deterministic - * than {@link #invokeExactMethod(Object, String, Object[], Class[])}. - * It loops through all methods with names that match - * and then executes the first it finds with compatable parameters.

    - * - *

    This method supports calls to methods taking primitive parameters - * via passing in wrapping classes. So, for example, a Boolean class - * would match a boolean primitive.

    - * - *

    This is a convenient wrapper for - * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}. - *

    - * - * @param objectClass invoke static method on this class - * @param methodName get method with this name - * @param arg use this argument - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - * @since 1.8.0 - */ - public static Object invokeStaticMethod( - Class objectClass, - String methodName, - Object arg) - throws - NoSuchMethodException, - IllegalAccessException, - InvocationTargetException { - - Object[] args = {arg}; - return invokeStaticMethod (objectClass, methodName, args); - - } - - - /** - *

    Invoke a named static method whose parameter type matches the object type.

    - * - *

    The behaviour of this method is less deterministic - * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}. - * It loops through all methods with names that match - * and then executes the first it finds with compatable parameters.

    - * - *

    This method supports calls to methods taking primitive parameters - * via passing in wrapping classes. So, for example, a Boolean class - * would match a boolean primitive.

    - * - *

    This is a convenient wrapper for - * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}. - *

    - * - * @param objectClass invoke static method on this class - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - * @since 1.8.0 - */ - public static Object invokeStaticMethod( - Class objectClass, - String methodName, - Object[] args) - throws - NoSuchMethodException, - IllegalAccessException, - InvocationTargetException { - - if (args == null) { - args = EMPTY_OBJECT_ARRAY; - } - int arguments = args.length; - Class[] parameterTypes = new Class[arguments]; - for (int i = 0; i < arguments; i++) { - parameterTypes[i] = args[i].getClass(); - } - return invokeStaticMethod (objectClass, methodName, args, parameterTypes); - - } - - - /** - *

    Invoke a named static method whose parameter type matches the object type.

    - * - *

    The behaviour of this method is less deterministic - * than {@link - * #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}. - * It loops through all methods with names that match - * and then executes the first it finds with compatable parameters.

    - * - *

    This method supports calls to methods taking primitive parameters - * via passing in wrapping classes. So, for example, a Boolean class - * would match a boolean primitive.

    - * - * - * @param objectClass invoke static method on this class - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @param parameterTypes match these parameters - treat null as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - * @since 1.8.0 - */ - public static Object invokeStaticMethod( - Class objectClass, - String methodName, - Object[] args, - Class[] parameterTypes) - throws - NoSuchMethodException, - IllegalAccessException, - InvocationTargetException { - - if (parameterTypes == null) { - parameterTypes = EMPTY_CLASS_PARAMETERS; - } - if (args == null) { - args = EMPTY_OBJECT_ARRAY; - } - - Method method = getMatchingAccessibleMethod( - objectClass, - methodName, - parameterTypes); - if (method == null) { - throw new NoSuchMethodException("No such accessible method: " + - methodName + "() on class: " + objectClass.getName()); - } - return method.invoke(null, args); - } - - - /** - *

    Invoke a static method whose parameter type matches exactly the object - * type.

    - * - *

    This is a convenient wrapper for - * {@link #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args)}. - *

    - * - * @param objectClass invoke static method on this class - * @param methodName get method with this name - * @param arg use this argument - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - * @since 1.8.0 - */ - public static Object invokeExactStaticMethod( - Class objectClass, - String methodName, - Object arg) - throws - NoSuchMethodException, - IllegalAccessException, - InvocationTargetException { - - Object[] args = {arg}; - return invokeExactStaticMethod (objectClass, methodName, args); - - } - - - /** - *

    Invoke a static method whose parameter types match exactly the object - * types.

    - * - *

    This uses reflection to invoke the method obtained from a call to - * {@link #getAccessibleMethod(Class, String, Class[])}.

    - * - * @param objectClass invoke static method on this class - * @param methodName get method with this name - * @param args use these arguments - treat null as empty array - * @return The value returned by the invoked method - * - * @throws NoSuchMethodException if there is no such accessible method - * @throws InvocationTargetException wraps an exception thrown by the - * method invoked - * @throws IllegalAccessException if the requested method is not accessible - * via reflection - * @since 1.8.0 - */ - public static Object invokeExactStaticMethod( - Class objectClass, - String methodName, - Object[] args) - throws - NoSuchMethodException, - IllegalAccessException, - InvocationTargetException { - if (args == null) { - args = EMPTY_OBJECT_ARRAY; - } - int arguments = args.length; - Class[] parameterTypes = new Class[arguments]; - for (int i = 0; i < arguments; i++) { - parameterTypes[i] = args[i].getClass(); - } - return invokeExactStaticMethod(objectClass, methodName, args, parameterTypes); - - } - - /** - *

    Return an accessible method (that is, one that can be invoked via - * reflection) with given name and parameters. If no such method - * can be found, return null. - * This is just a convenient wrapper for - * {@link #getAccessibleMethod(Method method)}.

    - * - * @param clazz get method from this class - * @param methodName get method with this name - * @param parameterTypes with these parameters types - * @return The accessible method - */ - @SuppressWarnings("unchecked") - public static Method getAccessibleMethod( - Class clazz, - String methodName, - Class[] parameterTypes) { - - try { - MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true); - // Check the cache first - Method method = getCachedMethod(md); - if (method != null) { - return method; - } - - method = getAccessibleMethod - (clazz, clazz.getMethod(methodName, parameterTypes)); - cacheMethod(md, method); - return method; - } catch (NoSuchMethodException e) { - return (null); - } - - } - - - /** - *

    Return an accessible method (that is, one that can be invoked via - * reflection) that implements the specified Method. If no such method - * can be found, return null.

    - * - * @param method The method that we wish to call - * @return The accessible method - */ - public static Method getAccessibleMethod(Method method) { - - // Make sure we have a method to check - if (method == null) { - return (null); - } - - return getAccessibleMethod(method.getDeclaringClass(), method); - - } - - /** - *

    Return an accessible method (that is, one that can be invoked via - * reflection) that implements the specified Method. If no such method - * can be found, return null.

    - * - * @param clazz The class of the object - * @param method The method that we wish to call - * @return The accessible method - * @since 1.8.0 - */ - public static Method getAccessibleMethod(Class clazz, Method method) { - - // Make sure we have a method to check - if (method == null) { - return (null); - } - - // If the requested method is not public we cannot call it - if (!Modifier.isPublic(method.getModifiers())) { - return (null); - } - - boolean sameClass = true; - if (clazz == null) { - clazz = method.getDeclaringClass(); - } else { - sameClass = clazz.equals(method.getDeclaringClass()); - if (!method.getDeclaringClass().isAssignableFrom(clazz)) { - throw new IllegalArgumentException(clazz.getName() + - " is not assignable from " + method.getDeclaringClass().getName()); - } - } - - // If the class is public, we are done - if (Modifier.isPublic(clazz.getModifiers())) { - if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { - setMethodAccessible(method); // Default access superclass workaround - } - return (method); - } - - String methodName = method.getName(); - Class[] parameterTypes = method.getParameterTypes(); - - // Check the implemented interfaces and subinterfaces - method = - getAccessibleMethodFromInterfaceNest(clazz, - methodName, - parameterTypes); - - // Check the superclass chain - if (method == null) { - method = getAccessibleMethodFromSuperclass(clazz, - methodName, - parameterTypes); - } - - return (method); - - } - - - // -------------------------------------------------------- Private Methods - - /** - *

    Return an accessible method (that is, one that can be invoked via - * reflection) by scanning through the superclasses. If no such method - * can be found, return null.

    - * - * @param clazz Class to be checked - * @param methodName Method name of the method we wish to call - * @param parameterTypes The parameter type signatures - */ - @SuppressWarnings("unchecked") - private static Method getAccessibleMethodFromSuperclass - (Class clazz, String methodName, Class[] parameterTypes) { - - Class parentClazz = clazz.getSuperclass(); - while (parentClazz != null) { - if (Modifier.isPublic(parentClazz.getModifiers())) { - try { - return parentClazz.getMethod(methodName, parameterTypes); - } catch (NoSuchMethodException e) { - return null; - } - } - parentClazz = parentClazz.getSuperclass(); - } - return null; - } - - /** - *

    Return an accessible method (that is, one that can be invoked via - * reflection) that implements the specified method, by scanning through - * all implemented interfaces and subinterfaces. If no such method - * can be found, return null.

    - * - *

    There isn't any good reason why this method must be private. - * It is because there doesn't seem any reason why other classes should - * call this rather than the higher level methods.

    - * - * @param clazz Parent class for the interfaces to be checked - * @param methodName Method name of the method we wish to call - * @param parameterTypes The parameter type signatures - */ - @SuppressWarnings("unchecked") - private static Method getAccessibleMethodFromInterfaceNest - (Class clazz, String methodName, Class[] parameterTypes) { - - Method method = null; - - // Search up the superclass chain - for (; clazz != null; clazz = clazz.getSuperclass()) { - - // Check the implemented interfaces of the parent class - Class[] interfaces = clazz.getInterfaces(); - for (int i = 0; i < interfaces.length; i++) { - - // Is this interface public? - if (!Modifier.isPublic(interfaces[i].getModifiers())) { - continue; - } - - // Does the method exist on this interface? - try { - method = interfaces[i].getDeclaredMethod(methodName, - parameterTypes); - } catch (NoSuchMethodException e) { - /* Swallow, if no method is found after the loop then this - * method returns null. - */ - } - if (method != null) { - return method; - } - - // Recursively check our parent interfaces - method = - getAccessibleMethodFromInterfaceNest(interfaces[i], - methodName, - parameterTypes); - if (method != null) { - return method; - } - - } - - } - - // We did not find anything - return (null); - - } - - /** - *

    Find an accessible method that matches the given name and has compatible parameters. - * Compatible parameters mean that every method parameter is assignable from - * the given parameters. - * In other words, it finds a method with the given name - * that will take the parameters given.

    - * - *

    This method is slightly undeterminstic since it loops - * through methods names and return the first matching method.

    - * - *

    This method is used by - * {@link - * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}. - * - *

    This method can match primitive parameter by passing in wrapper classes. - * For example, a Boolean will match a primitive boolean - * parameter. - * - * @param clazz find method in this class - * @param methodName find method with this name - * @param parameterTypes find method with compatible parameters - * @return The accessible method - */ - @SuppressWarnings("unchecked") - public static Method getMatchingAccessibleMethod( - Class clazz, - String methodName, - Class[] parameterTypes) { - - MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false); - Logger log = tryGetLogger(); - - // see if we can find the method directly - // most of the time this works and it's much faster - try { - // Check the cache first - Method method = getCachedMethod(md); - if (method != null) { - return method; - } - - method = clazz.getMethod(methodName, parameterTypes); - - setMethodAccessible(method); // Default access superclass workaround - - cacheMethod(md, method); - return method; - - } catch (NoSuchMethodException e) { /* SWALLOW */ } - - // search through all methods - int paramSize = parameterTypes.length; - Method bestMatch = null; - Method[] methods = clazz.getMethods(); - float bestMatchCost = Float.MAX_VALUE; - float myCost = Float.MAX_VALUE; - for (int i = 0, size = methods.length; i < size ; i++) { - if (methods[i].getName().equals(methodName)) { - - // compare parameters - Class[] methodsParams = methods[i].getParameterTypes(); - int methodParamSize = methodsParams.length; - if (methodParamSize == paramSize) { - boolean match = true; - for (int n = 0 ; n < methodParamSize; n++) { - if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) { - match = false; - break; - } - } - - if (match) { - // get accessible version of method - Method method = getAccessibleMethod(clazz, methods[i]); - if (method != null) { - setMethodAccessible(method); // Default access superclass workaround - myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes()); - if ( myCost < bestMatchCost ) { - bestMatch = method; - bestMatchCost = myCost; - } - } - - if (log != null) { - //log.severe("Couldn't find accessible method."); - } - } - } - } - } - if ( bestMatch != null ){ - cacheMethod(md, bestMatch); - } else { - if (log != null) { - log.severe("No match found."); - } - } - - return bestMatch; - } - - /** - * Attempt to get the default logger from Bukkit. - * @return Bukkit default logger. - */ - private static Logger tryGetLogger() { - try { - return Bukkit.getLogger(); - } catch (Exception e) { - return null; - } - } - - /** - * Try to make the method accessible - * @param method The source arguments - */ - private static void setMethodAccessible(Method method) { - try { - // - // - // When a public class has a default access superclass - // with public methods, these methods are accessible. - // Calling them from compiled code works fine. - // - // Unfortunately, using reflection to invoke these methods - // seems to (wrongly) to prevent access even when the method - // modifer is public. - // - // The following workaround solves the problem but will only - // work from sufficiently privilages code. - // - // Better workarounds would be greatfully accepted. - // - if (!method.isAccessible()) { - method.setAccessible(true); - } - - } catch (SecurityException se) { - - if (!loggedAccessibleWarning) { - boolean vulnerableJVM = false; - try { - String specVersion = System.getProperty("java.specification.version"); - if (specVersion.charAt(0) == '1' && - (specVersion.charAt(2) == '0' || - specVersion.charAt(2) == '1' || - specVersion.charAt(2) == '2' || - specVersion.charAt(2) == '3')) { - - vulnerableJVM = true; - } - } catch (SecurityException e) { - // don't know - so display warning - vulnerableJVM = true; - } - if (vulnerableJVM && tryGetLogger() != null) { - tryGetLogger().info("Vulnerable JVM!"); - } - - loggedAccessibleWarning = true; - } - } - } - - /** - * Returns the sum of the object transformation cost for each class in the source - * argument list. - * @param srcArgs The source arguments - * @param destArgs The destination arguments - * @return The total transformation cost - */ - private static float getTotalTransformationCost(Class[] srcArgs, Class[] destArgs) { - - float totalCost = 0.0f; - for (int i = 0; i < srcArgs.length; i++) { - Class srcClass, destClass; - srcClass = srcArgs[i]; - destClass = destArgs[i]; - totalCost += getObjectTransformationCost(srcClass, destClass); - } - - return totalCost; - } - - /** - * Gets the number of steps required needed to turn the source class into the - * destination class. This represents the number of steps in the object hierarchy - * graph. - * @param srcClass The source class - * @param destClass The destination class - * @return The cost of transforming an object - */ - private static float getObjectTransformationCost(Class srcClass, Class destClass) { - float cost = 0.0f; - while (destClass != null && !destClass.equals(srcClass)) { - if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) { - // slight penalty for interface match. - // we still want an exact match to override an interface match, but - // an interface match should override anything where we have to get a - // superclass. - cost += 0.25f; - break; - } - cost++; - destClass = destClass.getSuperclass(); - } - - /* - * If the destination class is null, we've travelled all the way up to - * an Object match. We'll penalize this by adding 1.5 to the cost. - */ - if (destClass == null) { - cost += 1.5f; - } - - return cost; - } - - - /** - *

    Determine whether a type can be used as a parameter in a method invocation. - * This method handles primitive conversions correctly.

    - * - *

    In order words, it will match a Boolean to a boolean, - * a Long to a long, - * a Float to a float, - * a Integer to a int, - * and a Double to a double. - * Now logic widening matches are allowed. - * For example, a Long will not match a int. - * - * @param parameterType the type of parameter accepted by the method - * @param parameterization the type of parameter being tested - * - * @return true if the assignement is compatible. - */ - @SuppressWarnings("unchecked") - public static final boolean isAssignmentCompatible(Class parameterType, Class parameterization) { - // try plain assignment - if (parameterType.isAssignableFrom(parameterization)) { - return true; - } - - if (parameterType.isPrimitive()) { - // this method does *not* do widening - you must specify exactly - // is this the right behaviour? - Class parameterWrapperClazz = getPrimitiveWrapper(parameterType); - if (parameterWrapperClazz != null) { - return parameterWrapperClazz.equals(parameterization); - } - } - - return false; - } - - /** - * Gets the wrapper object class for the given primitive type class. - * For example, passing boolean.class returns Boolean.class - * @param primitiveType the primitive type class for which a match is to be found - * @return the wrapper type associated with the given primitive - * or null if no match is found - */ - public static Class getPrimitiveWrapper(Class primitiveType) { - // does anyone know a better strategy than comparing names? - if (boolean.class.equals(primitiveType)) { - return Boolean.class; - } else if (float.class.equals(primitiveType)) { - return Float.class; - } else if (long.class.equals(primitiveType)) { - return Long.class; - } else if (int.class.equals(primitiveType)) { - return Integer.class; - } else if (short.class.equals(primitiveType)) { - return Short.class; - } else if (byte.class.equals(primitiveType)) { - return Byte.class; - } else if (double.class.equals(primitiveType)) { - return Double.class; - } else if (char.class.equals(primitiveType)) { - return Character.class; - } else { - - return null; - } - } - - /** - * Gets the class for the primitive type corresponding to the primitive wrapper class given. - * For example, an instance of Boolean.class returns a boolean.class. - * @param wrapperType the - * @return the primitive type class corresponding to the given wrapper class, - * null if no match is found - */ - public static Class getPrimitiveType(Class wrapperType) { - // does anyone know a better strategy than comparing names? - if (Boolean.class.equals(wrapperType)) { - return boolean.class; - } else if (Float.class.equals(wrapperType)) { - return float.class; - } else if (Long.class.equals(wrapperType)) { - return long.class; - } else if (Integer.class.equals(wrapperType)) { - return int.class; - } else if (Short.class.equals(wrapperType)) { - return short.class; - } else if (Byte.class.equals(wrapperType)) { - return byte.class; - } else if (Double.class.equals(wrapperType)) { - return double.class; - } else if (Character.class.equals(wrapperType)) { - return char.class; - } else { - return null; - } - } - - /** - * Find a non primitive representation for given primitive class. - * - * @param clazz the class to find a representation for, not null - * @return the original class if it not a primitive. Otherwise the wrapper class. Not null - */ - public static Class toNonPrimitiveClass(Class clazz) { - if (clazz.isPrimitive()) { - Class primitiveClazz = MethodUtils.getPrimitiveWrapper(clazz); - // the above method returns - if (primitiveClazz != null) { - return primitiveClazz; - } else { - return clazz; - } - } else { - return clazz; - } - } - - - /** - * Return the method from the cache, if present. - * - * @param md The method descriptor - * @return The cached method - */ - private static Method getCachedMethod(MethodDescriptor md) { - if (CACHE_METHODS) { - Reference methodRef = (Reference)cache.get(md); - if (methodRef != null) { - return (Method)methodRef.get(); - } - } - return null; - } - - /** - * Add a method to the cache. - * - * @param md The method descriptor - * @param method The method to cache - */ - @SuppressWarnings("unchecked") - private static void cacheMethod(MethodDescriptor md, Method method) { - if (CACHE_METHODS) { - if (method != null) { - cache.put(md, new WeakReference(method)); - } - } - } - - /** - * Represents the key to looking up a Method by reflection. - */ - private static class MethodDescriptor { - private Class cls; - private String methodName; - private Class[] paramTypes; - private boolean exact; - private int hashCode; - - /** - * The sole constructor. - * - * @param cls the class to reflect, must not be null - * @param methodName the method name to obtain - * @param paramTypes the array of classes representing the paramater types - * @param exact whether the match has to be exact. - */ - public MethodDescriptor(Class cls, String methodName, Class[] paramTypes, boolean exact) { - if (cls == null) { - throw new IllegalArgumentException("Class cannot be null"); - } - if (methodName == null) { - throw new IllegalArgumentException("Method Name cannot be null"); - } - if (paramTypes == null) { - paramTypes = EMPTY_CLASS_PARAMETERS; - } - - this.cls = cls; - this.methodName = methodName; - this.paramTypes = paramTypes; - this.exact= exact; - - this.hashCode = methodName.length(); - } - /** - * Checks for equality. - * @param obj object to be tested for equality - * @return true, if the object describes the same Method. - */ - @Override - public boolean equals(Object obj) { - if (!(obj instanceof MethodDescriptor)) { - return false; - } - MethodDescriptor md = (MethodDescriptor)obj; - - return ( - exact == md.exact && - methodName.equals(md.methodName) && - cls.equals(md.cls) && - java.util.Arrays.equals(paramTypes, md.paramTypes) - ); - } - /** - * Returns the string length of method name. I.e. if the - * hashcodes are different, the objects are different. If the - * hashcodes are the same, need to use the equals method to - * determine equality. - * @return the string length of method name. - */ - @Override - public int hashCode() { - return hashCode; - } - } -} diff --git a/src/main/java/com/comphenix/protocol/reflect/ObjectWriter.java b/src/main/java/com/comphenix/protocol/reflect/ObjectWriter.java index f0c963a3..82c773a0 100644 --- a/src/main/java/com/comphenix/protocol/reflect/ObjectWriter.java +++ b/src/main/java/com/comphenix/protocol/reflect/ObjectWriter.java @@ -2,127 +2,133 @@ * 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 + * 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. + * 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 + * 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 com.comphenix.protocol.PacketType; +import com.comphenix.protocol.injector.StructureCache; +import com.comphenix.protocol.injector.packet.PacketRegistry; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.StreamSerializer; 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; +import java.util.HashMap; +import java.util.Map; /** * Can copy an object field by field. - * + * * @author Kristian */ public class ObjectWriter { + // Cache structure modifiers - @SuppressWarnings("rawtypes") - private static ConcurrentMap> cache = - new ConcurrentHashMap>(); - + private static final Map, StructureModifier> CACHE = new HashMap<>(); + /** * 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 + + // Handle subclasses of the packet class with our custom structure cache, if possible if (!type.equals(packetClass) && packetClass.isAssignableFrom(type)) { - // Delegate to our already existing registry of structure modifiers - return StructureCache.getStructure(type); + // might be a packet, but some packets are not registered (for example PacketPlayInFlying, only the subtypes are present) + PacketType packetType = PacketRegistry.getPacketType(type); + if (packetType != null) { + // packet is present, delegate to the cache + return StructureCache.getStructure(packetType); + } } - - StructureModifier modifier = cache.get(type); - + // Create the structure modifier if we haven't already + StructureModifier modifier = CACHE.get(type); if (modifier == null) { - StructureModifier value = new StructureModifier(type, null, false); - modifier = cache.putIfAbsent(type, value); - - 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 source - fields to copy. * @param destination - fields to copy to. - * @param commonType - type containing each field to copy. + * @param commonType - type containing each field to copy. */ 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); + this.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. + * @param modifierDest - modifier for the new cloned object. + * @param fieldIndex - the current field index. */ - protected void transformField(StructureModifier modifierSource, StructureModifier modifierDest, int fieldIndex) { + 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 = getModifier(commonType); - + StructureModifier modifier = this.getModifier(commonType); + // Add target StructureModifier modifierSource = modifier.withTarget(source); StructureModifier modifierDest = modifier.withTarget(destination); - + // Copy every field try { for (int i = 0; i < modifierSource.size(); i++) { Field field = modifierSource.getField(i); int mod = field.getModifiers(); - + // 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); + this.transformField(modifierSource, modifierDest, i); } } - + // Copy private fields underneath -// Class superclass = commonType.getSuperclass(); -// -// if (superclass != null && !superclass.equals(Object.class)) { -// copyToInternal(source, destination, superclass, false); -// } - } catch (FieldAccessException e) { + Class superclass = commonType.getSuperclass(); + if (superclass != null && !superclass.equals(Object.class)) { + this.copyToInternal(source, destination, superclass, false); + } + } catch (Exception e) { throw new RuntimeException("Unable to copy fields from " + commonType.getName(), e); } } diff --git a/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java b/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java index 09cb92c7..58d0e773 100644 --- a/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java +++ b/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java @@ -17,6 +17,8 @@ package com.comphenix.protocol.reflect; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.google.common.primitives.Primitives; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -25,37 +27,13 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import com.google.common.primitives.Primitives; - /** * Used to print the content of an arbitrary class. - * + * * @author Kristian */ public class PrettyPrinter { - /** - * Represents a generic object printer. - * @author Kristian - */ - public interface ObjectPrinter { - public static final ObjectPrinter DEFAULT = new ObjectPrinter() { - @Override - public boolean print(StringBuilder output, Object value) { - return false; - } - }; - - /** - * Print the content of the given object. - *

    - * Return FALSE in order for let the default printer take over. - * @param output - where to print the output. - * @param value - the value to print, may be NULL. - * @return TRUE if we processed the value and added to the output, FALSE otherwise. - */ - public boolean print(StringBuilder output, Object value); - } - + /** * How far we will recurse. */ @@ -63,136 +41,150 @@ public class PrettyPrinter { /** * Print the contents of an object. + * * @param object - the object to serialize. * @return String representation of the class. * @throws IllegalAccessException If the object is null */ public static String printObject(Object object) throws IllegalAccessException { - if (object == null) + if (object == null) { throw new IllegalArgumentException("object cannot be NULL."); - + } + return printObject(object, object.getClass(), Object.class); } - + /** * Print the contents of an object. + * * @param object - the object to serialize. - * @param start - class to start at. - * @param stop - superclass that will stop the process. + * @param start - class to start at. + * @param stop - superclass that will stop the process. * @return String representation of the class * @throws IllegalAccessException If the object is null */ public static String printObject(Object object, Class start, Class stop) throws IllegalAccessException { - if (object == null) + if (object == null) { throw new IllegalArgumentException("object cannot be NULL."); - + } + return printObject(object, start, stop, RECURSE_DEPTH); } - + /** * Print the contents of an object. - * @param object - the object to serialize. - * @param start - class to start at. - * @param stop - superclass that will stop the process. + * + * @param object - the object to serialize. + * @param start - class to start at. + * @param stop - superclass that will stop the process. * @param hierachyDepth - maximum recursion level. * @return String representation of the class. * @throws IllegalAccessException If the object is null */ - public static String printObject(Object object, Class start, Class stop, int hierachyDepth) throws IllegalAccessException { + public static String printObject(Object object, Class start, Class stop, int hierachyDepth) + throws IllegalAccessException { return printObject(object, start, stop, hierachyDepth, ObjectPrinter.DEFAULT); } - + /** * Print the contents of an object. - * @param object - the object to serialize. - * @param start - class to start at. - * @param stop - superclass that will stop the process. + * + * @param object - the object to serialize. + * @param start - class to start at. + * @param stop - superclass that will stop the process. * @param hierachyDepth - maximum recursion level. - * @param printer - a generic object printer. + * @param printer - a generic object printer. * @return String representation of the class. * @throws IllegalAccessException If the object is null */ - public static String printObject(Object object, Class start, Class stop, int hierachyDepth, ObjectPrinter printer) throws IllegalAccessException { - if (object == null) + public static String printObject(Object object, Class start, Class stop, int hierachyDepth, + ObjectPrinter printer) throws IllegalAccessException { + if (object == null) { throw new IllegalArgumentException("object cannot be NULL."); - + } + StringBuilder output = new StringBuilder(); Set previous = new HashSet(); - + // Start and stop output.append("{ "); printObject(output, object, start, stop, previous, hierachyDepth, true, printer); output.append(" }"); - + return output.toString(); } - + @SuppressWarnings("rawtypes") private static void printIterables(StringBuilder output, Iterable iterable, Class stop, - Set previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException { - + Set previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException { + boolean first = true; output.append("("); - + for (Object value : iterable) { - if (first) + if (first) { first = false; - else + } else { output.append(", "); - + } + // Print value - printValue(output, value, stop, previous, hierachyIndex - 1, printer); + printValue(output, value, stop, previous, hierachyIndex - 1, printer); } - + output.append(")"); } /** * Print the content of a maps entries. - * @param output - the output string builder. - * @param map - the map to print. - * @param current - the type of this map. - * @param stop - the class that indicates we should stop printing. - * @param previous - previous objects printed. + * + * @param output - the output string builder. + * @param map - the map to print. + * @param current - the type of this map. + * @param stop - the class that indicates we should stop printing. + * @param previous - previous objects printed. * @param hierachyIndex - hierachy index. * @throws IllegalAccessException If any reflection went wrong. */ private static void printMap(StringBuilder output, Map map, Class current, Class stop, - Set previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException { + Set previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException { boolean first = true; output.append("["); for (Entry entry : map.entrySet()) { - if (first) + if (first) { first = false; - else + } else { output.append(", "); + } printValue(output, entry.getKey(), stop, previous, hierachyIndex - 1, printer); output.append(": "); - printValue(output, entry.getValue(), stop, previous, hierachyIndex - 1, printer); + printValue(output, entry.getValue(), stop, previous, hierachyIndex - 1, printer); } output.append("]"); } - + private static void printArray(StringBuilder output, Object array, Class current, Class stop, - Set previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException { - + Set previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException { + Class component = current.getComponentType(); boolean first = true; - - if (!component.isArray()) + + if (!component.isArray()) { output.append(component.getName()); + } output.append("["); - + for (int i = 0; i < Array.getLength(array); i++) { - if (first) + if (first) { first = false; - else + } else { output.append(", "); - + } + // Handle exceptions try { printValue(output, Array.get(array, i), component, stop, previous, hierachyIndex - 1, printer); @@ -204,15 +196,15 @@ public class PrettyPrinter { break; } } - + output.append("]"); } - + // Internal recursion method private static void printObject(StringBuilder output, Object object, Class current, Class stop, - Set previous, int hierachyIndex, boolean first, - ObjectPrinter printer) throws IllegalAccessException { - + Set previous, int hierachyIndex, boolean first, + ObjectPrinter printer) throws IllegalAccessException { + // See if we're supposed to skip this class if (current == null || current == Object.class || (stop != null && current.equals(stop))) { return; @@ -220,48 +212,47 @@ public class PrettyPrinter { // Don't iterate twice previous.add(object); - + // Hard coded limit if (hierachyIndex < 0) { output.append("..."); return; } - + for (Field field : current.getDeclaredFields()) { int mod = field.getModifiers(); - + // Skip a good number of the fields if (!Modifier.isTransient(mod) && !Modifier.isStatic(mod)) { Class type = field.getType(); - Object value = FieldUtils.readField(field, object, true); - + Object value = Accessors.getFieldAccessor(field).get(object); + if (first) { first = false; } else { output.append(", "); } - + output.append(field.getName()); output.append(" = "); printValue(output, value, type, stop, previous, hierachyIndex - 1, printer); } } - + // Recurse printObject(output, object, current.getSuperclass(), stop, previous, hierachyIndex, first, printer); } private static void printValue(StringBuilder output, Object value, Class stop, - Set previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException { + Set previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException { // Handle the NULL case printValue(output, value, value != null ? value.getClass() : null, stop, previous, hierachyIndex, printer); } - - @SuppressWarnings({"rawtypes", "unchecked"}) + private static void printValue(StringBuilder output, Object value, Class type, - Class stop, Set previous, int hierachyIndex, - ObjectPrinter printer) throws IllegalAccessException { - + Class stop, Set previous, int hierachyIndex, + ObjectPrinter printer) throws IllegalAccessException { + // Just print primitive types if (printer.print(output, value)) { return; @@ -274,7 +265,7 @@ public class PrettyPrinter { } else if (type.isArray()) { printArray(output, value, type, stop, previous, hierachyIndex, printer); } else if (Iterable.class.isAssignableFrom(type)) { - printIterables(output, (Iterable) value, stop, previous, hierachyIndex, printer); + printIterables(output, (Iterable) value, stop, previous, hierachyIndex, printer); } else if (Map.class.isAssignableFrom(type)) { printMap(output, (Map) value, type, stop, previous, hierachyIndex, printer); } else if (ClassLoader.class.isAssignableFrom(type) || previous.contains(value)) { @@ -286,4 +277,25 @@ public class PrettyPrinter { output.append(" }"); } } + + /** + * Represents a generic object printer. + * + * @author Kristian + */ + public interface ObjectPrinter { + + ObjectPrinter DEFAULT = (output, value) -> false; + + /** + * Print the content of the given object. + *

    + * Return FALSE in order for let the default printer take over. + * + * @param output - where to print the output. + * @param value - the value to print, may be NULL. + * @return TRUE if we processed the value and added to the output, FALSE otherwise. + */ + boolean print(StringBuilder output, Object value); + } } diff --git a/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java b/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java index 793fcac1..1f5def03 100644 --- a/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java +++ b/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java @@ -17,229 +17,220 @@ package com.comphenix.protocol.reflect; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; - -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.ProtocolLogger; -import com.comphenix.protocol.error.PluginContext; -import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.instances.BannedGenerator; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.InstanceProvider; import com.comphenix.protocol.utility.MinecraftReflection; -import com.google.common.base.Function; -import com.google.common.base.Objects; -import com.google.common.collect.ImmutableList; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; /** * Provides list-oriented access to the fields of a Minecraft packet. *

    * Implemented by using reflection. Use a CompiledStructureModifier, if speed is essential. - * + * + * @param Type of the fields to retrieve. * @author Kristian - * @param Type of the fields to retrieve. */ -@SuppressWarnings("rawtypes") -public class StructureModifier { - +public class StructureModifier { + + // Instance generator we will use + private static final DefaultInstances DEFAULT_GENERATOR = getDefaultGenerator(); + // a structure modifier which does nothing + private static final StructureModifier NO_OP_MODIFIER = new StructureModifier() { + @Override + public Object read(int fieldIndex) throws FieldAccessException { + return null; + } + + @Override + public StructureModifier write(int fieldIndex, Object value) throws FieldAccessException { + return this; + } + + @Override + protected FieldAccessor findFieldAccessor(int fieldIndex) { + return null; + } + }; + // Object and its type - protected Class targetType; protected Object target; - - // Converter. May be NULL. - protected EquivalentConverter converter; - + protected Class targetType; + // The fields to read in order - protected Class fieldType; - protected List data = new ArrayList<>(); - + protected Class fieldType; + protected List accessors = new ArrayList<>(); + + // Converter. May be NULL. + protected EquivalentConverter converter; + // Improved default values - protected Map defaultFields; - + protected Map defaultFields; // Cache of previous types - protected Map subtypeCache; - + protected Map, StructureModifier> subtypeCache; + // Whether or subclasses should handle conversion protected boolean customConvertHandling; - - // Whether or not to automatically compile the structure modifier - protected boolean useStructureCompiler; - - // Instance generator we wil use - private static DefaultInstances DEFAULT_GENERATOR = getDefaultGenerator(); + + /** + * Creates a structure modifier. + * + * @param targetType - the structure to modify. + */ + public StructureModifier(Class targetType) { + this(targetType, Object.class, true); + } + + /** + * Creates a structure modifier. + * + * @param targetType - the structure to modify. + * @param superclassExclude - a superclass to exclude. + * @param requireDefault - whether we will be using writeDefaults() + */ + public StructureModifier( + Class targetType, + Class superclassExclude, + boolean requireDefault + ) { + List fields = getFields(targetType, superclassExclude); + Map defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<>(); + + this.initialize(targetType, Object.class, fields, defaults, null, new HashMap<>()); + } + + /** + * Consumers of this method should call "initialize". + */ + protected StructureModifier() { + + } private static DefaultInstances getDefaultGenerator() { - final List providers = new ArrayList<>(); - + List providers = new ArrayList<>(); + // Prevent certain classes from being generated providers.add(new BannedGenerator(MinecraftReflection.getItemStackClass(), MinecraftReflection.getBlockClass())); providers.addAll(DefaultInstances.DEFAULT.getRegistered()); return DefaultInstances.fromCollection(providers); } - /** - * Creates a structure modifier. - * @param targetType - the structure to modify. - */ - public StructureModifier(Class targetType) { - this(targetType, null, true); + // Used to generate plausible default values + private static Map generateDefaultFields(Collection fields) { + int currentFieldIndex = 0; + Map requireDefaults = new HashMap<>(); + + for (FieldAccessor accessor : fields) { + Field field = accessor.getField(); + if (!field.getType().isPrimitive() && !Modifier.isFinal(field.getModifiers())) { + Object defaultInstance = DEFAULT_GENERATOR.getDefault(field.getType()); + if (defaultInstance != null) { + requireDefaults.put(accessor, currentFieldIndex); + } + } + + // increment the index of the processed fields + currentFieldIndex++; + } + + return requireDefaults; } - - /** - * Creates a structure modifier. - * @param targetType - the structure to modify. - * @param useStructureCompiler - whether or not to use a structure compiler. - */ - public StructureModifier(Class targetType, boolean useStructureCompiler) { - this(targetType, null, true, useStructureCompiler); + + // Used to filter out irrelevant fields + private static List getFields(Class type, Class superclassExclude) { + return FuzzyReflection.fromClass(type, true).getDeclaredFields(superclassExclude).stream() + .filter(field -> !Modifier.isStatic(field.getModifiers())) + .map(Accessors::getFieldAccessor) + .collect(Collectors.toList()); } - - /** - * Creates a structure modifier. - * @param targetType - the structure to modify. - * @param superclassExclude - a superclass to exclude. - * @param requireDefault - whether or not we will be using writeDefaults(). - */ - public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) { - this(targetType, superclassExclude, requireDefault, true); - } - - /** - * Creates a structure modifier. - * @param targetType - the structure to modify. - * @param superclassExclude - a superclass to exclude. - * @param requireDefault - whether or not we will be using writeDefaults(). - * @param useStructureCompiler - whether or not to automatically compile this structure modifier. - */ - public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault, boolean useStructureCompiler) { - List fields = getFields(targetType, superclassExclude); - Map defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<>(); - - initialize(targetType, Object.class, fields, defaults, null, new ConcurrentHashMap<>(), useStructureCompiler); - } - - /** - * Consumers of this method should call "initialize". - */ - protected StructureModifier() { - - } - + /** * Initialize using the same field types. + * * @param other - information to set. */ - protected void initialize(StructureModifier other) { - initialize(other.targetType, other.fieldType, other.data, - other.defaultFields, other.converter, other.subtypeCache, - other.useStructureCompiler); + protected void initialize(StructureModifier other) { + this.initialize( + other.targetType, + other.fieldType, + other.accessors, + other.defaultFields, + other.converter, + other.subtypeCache); } - + /** * Initialize every field of this class. - * @param targetType - type of the object we're reading and writing from. - * @param fieldType - the common type of the fields we're modifying. - * @param data - list of fields to modify. + * + * @param targetType - type of the object we're reading and writing from. + * @param fieldType - the common type of the fields we're modifying. + * @param data - list of fields to modify. * @param defaultFields - list of fields that will be automatically initialized. - * @param converter - converts between the common field type and the actual type the consumer expects. - * @param subTypeCache - a structure modifier cache. + * @param converter - converts between the common field type and the actual type the consumer expects. + * @param subTypeCache - a structure modifier cache. */ - protected void initialize(Class targetType, Class fieldType, - List data, Map defaultFields, - EquivalentConverter converter, Map subTypeCache) { - initialize(targetType, fieldType, data, defaultFields, converter, subTypeCache, true); - } - - /** - * Initialize every field of this class. - * @param targetType - type of the object we're reading and writing from. - * @param fieldType - the common type of the fields we're modifying. - * @param data - list of fields to modify. - * @param defaultFields - list of fields that will be automatically initialized. - * @param converter - converts between the common field type and the actual type the consumer expects. - * @param subTypeCache - a structure modifier cache. - * @param useStructureCompiler - whether or not to automatically compile this structure modifier. - */ - protected void initialize(Class targetType, Class fieldType, - List data, Map defaultFields, - EquivalentConverter converter, Map subTypeCache, - boolean useStructureCompiler) { - + protected void initialize( + Class targetType, + Class fieldType, + List data, + Map defaultFields, + EquivalentConverter converter, + Map, StructureModifier> subTypeCache + ) { this.targetType = targetType; this.fieldType = fieldType; - this.data = data; + this.accessors = data; this.defaultFields = defaultFields; this.converter = converter; this.subtypeCache = subTypeCache; - this.useStructureCompiler = useStructureCompiler; } /** * Reads the value of a field given its index. *

    * Note: This method is prone to exceptions (there are currently 5 total throw statements). It is recommended that you - * use {@link #readSafely(int)}, which returns {@code null} if the field doesn't exist, instead of throwing an exception. - * + * use {@link #readSafely(int)}, which returns {@code null} if the field doesn't exist, instead of throwing an + * exception. + * * @param fieldIndex - index of the field. * @return Value of the field. - * @throws FieldAccessException if the field doesn't exist, or it cannot be accessed under the current security contraints. + * @throws FieldAccessException if the given field index is out of bounds. + * @throws IllegalStateException if this modifier has no target set. */ - public TField read(int fieldIndex) throws FieldAccessException { - try { - return readInternal(fieldIndex); - } catch (FieldAccessException ex) { - String plugin = PluginContext.getPluginCaller(ex); - if (ProtocolLibrary.INCOMPATIBLE.contains(plugin)) { - ProtocolLogger.log(Level.WARNING, "Encountered an exception caused by incompatible plugin {0}.", plugin); - ProtocolLogger.log(Level.WARNING, "It is advised that you remove it."); - } - - throw ex; + public T read(int fieldIndex) throws FieldAccessException { + FieldAccessor accessor = this.findFieldAccessor(fieldIndex); + if (accessor == null) { + throw FieldAccessException.fromFormat( + "Field index %d is out of bounds for length %s", + fieldIndex, + this.accessors.size()); } + + return this.readInternal(accessor); } - @SuppressWarnings("unchecked") - private TField readInternal(int fieldIndex) throws FieldAccessException { - if (target == null) - throw new IllegalStateException("Cannot read from a null target!"); - - if (fieldIndex < 0) - throw new FieldAccessException(String.format("Field index (%s) cannot be negative.", fieldIndex)); - - if (data.size() == 0) - throw new FieldAccessException(String.format("No field with type %s exists in class %s.", fieldType.getName(), - target.getClass().getSimpleName())); - - if (fieldIndex >= data.size()) - throw new FieldAccessException(String.format("Field index out of bounds. (Index: %s, Size: %s)", fieldIndex, data.size())); - - try { - Object result = FieldUtils.readField(data.get(fieldIndex), target, true); - - // Use the converter, if we have it - if (needConversion()) { - return converter.getSpecific(result); - } else { - return (TField) result; - } - } catch (IllegalAccessException e) { - throw new FieldAccessException("Cannot read field due to a security limitation.", e); - } - } - /** * Reads the value of a field only if it exists. If the field does not exist, {@code null} is returned. *

    - * As its name implies, this method is a much safer alternative to {@link #read(int)}. - * In addition to throwing less exceptions and thereby causing less console spam, this - * method makes providing backwards compatiblity signficiantly easier, as shown below: - * + * As its name implies, this method is a much safer alternative to {@link #read(int)}. In addition to throwing less + * exceptions and thereby causing less console spam, this method makes providing backwards compatiblity signficiantly + * easier, as shown below: + * *

    
     	 * BlockPosition position = packet.getBlockPositionModifier().readSafely(0);
     	 * if (position != null) {
    @@ -248,17 +239,13 @@ public class StructureModifier {
     	 *     // Handle 1.7-
     	 * }
     	 * 
    - * + * * @param fieldIndex - index of the field. * @return Value of the field, or NULL if it doesn't exist. - * @throws FieldAccessException if the field cannot be accessed under the current security constraints. + * @throws IllegalStateException if this modifier has no target set. */ - public TField readSafely(int fieldIndex) throws FieldAccessException { - if (fieldIndex >= 0 && fieldIndex < data.size()) { - return read(fieldIndex); - } else { - return null; - } + public T readSafely(int fieldIndex) throws FieldAccessException { + return this.readInternal(this.findFieldAccessor(fieldIndex)); } /** @@ -271,465 +258,394 @@ public class StructureModifier { * @return An optional that may contain the value of the field * @see #readSafely(int) */ - public Optional optionRead(int fieldIndex) { - try { - return Optional.ofNullable(read(fieldIndex)); - } catch (FieldAccessException ex) { - return Optional.empty(); - } + public Optional optionRead(int fieldIndex) { + return Optional.ofNullable(this.readSafely(fieldIndex)); } - - /** - * Determine whether or not a field is read-only (final). - * @param fieldIndex - index of the field. - * @return TRUE if the field by the given index is read-only, FALSE otherwise. - */ - public boolean isReadOnly(int fieldIndex) { - 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()); - } - - /** - * Set whether or not a field should be treated as read only. - *

    - * Note that changing the read-only state to TRUE will only work if the current - * field was recently read-only or the current structure modifier hasn't been compiled yet. - * - * @param fieldIndex - index of the field. - * @param value - TRUE if this field should be read only, FALSE otherwise. - * @throws FieldAccessException If we cannot modify the read-only status. - * @deprecated In recent java versions (starting at 9) the modifier field is secured and will not be writeable. - */ - @Deprecated - public void setReadOnly(int fieldIndex, boolean value) throws FieldAccessException { - if (fieldIndex < 0 || fieldIndex >= data.size()) - throw new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")"); - try { - StructureModifier.setFinalState(data.get(fieldIndex), value); - } catch (IllegalAccessException e) { - throw new FieldAccessException("Cannot write read only status due to a security limitation.", e); + @SuppressWarnings("unchecked") + private T readInternal(FieldAccessor accessor) { + // just return null if the accessor is null + if (accessor == null) { + return null; } + + // don't try to convert null, just return null + Object fieldValue = accessor.get(this.target); + if (fieldValue == null) { + return null; + } + + // check if we need to convert the field value and do so if needed + return this.needConversion() ? this.converter.getSpecific(fieldValue) : (T) fieldValue; } - - /** - * Alter the final status of a field. - * @param field - the field to change. - * @param isReadOnly - TRUE if the field should be read only, FALSE otherwise. - * @throws IllegalAccessException If an error occured. - * @deprecated In recent java versions (starting at 9) the modifier field is secured and will not be writeable. - */ - @Deprecated - protected static void setFinalState(Field field, boolean isReadOnly) throws IllegalAccessException { - if (isReadOnly) - FieldUtils.writeField((Object) field, "modifiers", field.getModifiers() | Modifier.FINAL, true); - else - FieldUtils.writeField((Object) field, "modifiers", field.getModifiers() & ~Modifier.FINAL, true); - } - + /** * Writes the value of a field given its index. + * * @param fieldIndex - index of the field. - * @param value - new value of the field. + * @param value - new value of the field. * @return This structure modifier - for chaining. - * @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints. + * @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security + * contraints. */ - public StructureModifier write(int fieldIndex, TField value) throws FieldAccessException { - try { - return writeInternal(fieldIndex, value); - } catch (FieldAccessException ex) { - String plugin = PluginContext.getPluginCaller(ex); - if (ProtocolLibrary.INCOMPATIBLE.contains(plugin)) { - ProtocolLogger.log(Level.WARNING, "Encountered an exception caused by incompatible plugin {0}.", plugin); - ProtocolLogger.log(Level.WARNING, "It is advised that you remove it."); - } - - throw ex; - } - } - - private StructureModifier writeInternal(int fieldIndex, TField value) throws FieldAccessException { - if (target == null) - throw new IllegalStateException("Cannot read from a null target!"); - - if (fieldIndex < 0) - throw new FieldAccessException(String.format("Field index (%s) cannot be negative.", fieldIndex)); - - if (data.size() == 0) - throw new FieldAccessException(String.format("No field with type %s exists in class %s.", fieldType.getName(), - target.getClass().getSimpleName())); - - if (fieldIndex >= data.size()) - throw new FieldAccessException(String.format("Field index out of bounds. (Index: %s, Size: %s)", fieldIndex, data.size())); - - // Use the converter, if it exists - Object obj = needConversion() ? converter.getGeneric(value) : value; - - try { - FieldUtils.writeField(data.get(fieldIndex), target, obj, true); - } catch (IllegalAccessException e) { - throw new FieldAccessException("Cannot read field due to a security limitation.", e); + public StructureModifier write(int fieldIndex, T value) throws FieldAccessException { + FieldAccessor accessor = this.findFieldAccessor(fieldIndex); + if (accessor == null) { + throw FieldAccessException.fromFormat( + "Field index %d is out of bounds for length %s", + fieldIndex, + this.accessors.size()); } - // Make this method chainable - return this; + return this.writeInternal(accessor, value); } - - /** - * Retrieve the type of a specified field. - * @param index - the index. - * @return The type of the given field. - */ - protected Class getFieldType(int index) { - return data.get(index).getType(); - } - - /** - * Whether or not we should use the converter instance. - * @return TRUE if we should, FALSE otherwise. - */ - private final boolean needConversion() { - return converter != null && !customConvertHandling; - } - + /** * Writes the value of a given field IF and ONLY if it exists. + * * @param fieldIndex - index of the potential field. - * @param value - new value of the field. + * @param value - new value of the field. * @return This structure modifer - for chaining. * @throws FieldAccessException The field cannot be accessed under the current security contraints. */ - public StructureModifier writeSafely(int fieldIndex, TField value) throws FieldAccessException { - if (fieldIndex >= 0 && fieldIndex < data.size()) { - write(fieldIndex, value); - } - return this; + public StructureModifier writeSafely(int fieldIndex, T value) throws FieldAccessException { + FieldAccessor accessor = this.findFieldAccessor(fieldIndex); + return this.writeInternal(accessor, value); } - + /** * Correctly modifies the value of a field. + * * @param fieldIndex - index of the field to modify. - * @param select - the function that modifies the field value. + * @param select - the function that modifies the field value. * @return This structure modifier - for chaining. * @throws FieldAccessException The field cannot be accessed under the current security contraints. */ - public StructureModifier modify(int fieldIndex, Function select) throws FieldAccessException { - TField value = read(fieldIndex); - return write(fieldIndex, select.apply(value)); + public StructureModifier modify(int fieldIndex, UnaryOperator select) throws FieldAccessException { + T value = this.read(fieldIndex); + return this.write(fieldIndex, select.apply(value)); } - + + private StructureModifier writeInternal(FieldAccessor accessor, T value) throws FieldAccessException { + // just ignore if the accessor is not present + if (accessor == null) { + return this; + } + + // just write the value if the specific given one is null or no conversion is needed + if (value == null || !this.needConversion()) { + accessor.set(this.target, value); + return this; + } + + // convert and write + accessor.set(this.target, this.converter.getGeneric(value)); + return this; + } + + protected FieldAccessor findFieldAccessor(int fieldIndex) { + if (this.target == null) { + throw new IllegalStateException("Cannot read from modifier which has no target!"); + } + + // check if the field is out of bounds + if (fieldIndex < 0 || fieldIndex >= this.accessors.size()) { + return null; + } + + return this.accessors.get(fieldIndex); + } + /** - * Retrieves a structure modifier that only reads and writes fields of a given type. - * @param Type - * @param fieldType - the type, or supertype, of every field to modify. - * @return A structure modifier for fields of this type. + * Whether we should use the converter instance. + * + * @return TRUE if we should, FALSE otherwise. */ - public StructureModifier withType(Class fieldType) { - return withType(fieldType, null); + private boolean needConversion() { + return this.converter != null && !this.customConvertHandling; } - + /** * Sets all non-primitive fields to a more fitting default value. See {@link DefaultInstances#getDefault(Class)}. + * * @return The current structure modifier - for chaining. * @throws FieldAccessException If we're unable to write to the fields due to a security limitation. */ - public StructureModifier writeDefaults() throws FieldAccessException { - DefaultInstances generator = DefaultInstances.DEFAULT; - + public StructureModifier writeDefaults() throws FieldAccessException { // Write a default instance to every field - for (Field field : defaultFields.keySet()) { - try { - // Special case for Spigot's custom chat components - // They must be null or messages will be blank - if (field.getType().getCanonicalName().equals("net.md_5.bungee.api.chat.BaseComponent[]")) { - FieldUtils.writeField(field, target, null, true); - continue; - } - - FieldUtils.writeField(field, target, generator.getDefault(field.getType()), true); - } catch (IllegalAccessException e) { - throw new FieldAccessException("Cannot write to field due to a security limitation.", e); + for (FieldAccessor accessor : this.defaultFields.keySet()) { + // Special case for Spigot's custom chat components + // They must be null or messages will be blank + Field field = accessor.getField(); + if (field.getType().getCanonicalName().equals("net.md_5.bungee.api.chat.BaseComponent[]")) { + accessor.set(this.target, null); + continue; } + + // get the default value and write the field + Object defaultValue = DEFAULT_GENERATOR.getDefault(field.getType()); + accessor.set(this.target, defaultValue); } return this; } - private static Type[] getParamTypes(Field field) { - Type genericType = field.getGenericType(); - if (genericType instanceof ParameterizedType) { - return ((ParameterizedType) genericType).getActualTypeArguments(); - } - - return new Class[0]; - } - - /** - * Retrieves a structure modifier that only reads and writes fields of a given type. - * @param Type - * @param fieldType - the type, or supertype, of every field to modify. - * @param converter - converts objects into the given type. - * @return A structure modifier for fields of this type. - */ - public StructureModifier withType(Class fieldType, EquivalentConverter converter) { - return withParamType(fieldType, converter); - } - - /** - * Retrieves a structure modifier that only reads and writes fields of a given type. - * @param Type - * @param fieldType - the type, or supertype, of every field to modify. - * @param converter - converts objects into the given type. - * @param paramTypes - field type parameters - * @return A structure modifier for fields of this type. - */ - @SuppressWarnings("unchecked") - public StructureModifier withParamType(Class fieldType, EquivalentConverter converter, Class... paramTypes) { - if (fieldType == null) { - // It's not supported in this version, so return an empty modifier - return new StructureModifier() { - @Override - public T read(int index) { return null; } - - @Override - public StructureModifier write(int index, T value) { return this; } - }; - } - - StructureModifier result = subtypeCache.get(fieldType); - - // Do we need to update the cache? - if (result == null) { - List filtered = new ArrayList<>(); - Map defaults = new HashMap<>(); - int index = 0; - - for (Field field : data) { - if (fieldType.isAssignableFrom(field.getType())) { - if (paramTypes.length == 0 || Arrays.equals(getParamTypes(field), paramTypes)) { - filtered.add(field); - - // Don't use the original index - if (defaultFields.containsKey(field)) - defaults.put(field, index); - } - } - - // Keep track of the field index - index++; - } - - // Cache structure modifiers - result = withFieldType(fieldType, filtered, defaults); - - subtypeCache.put(fieldType, result); - - // Automatically compile the structure modifier - if (useStructureCompiler && BackgroundCompiler.getInstance() != null) - BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType); - } - - // Add the target too - result = result.withTarget(target); - - // And the converter, if it's needed - if (!Objects.equal(result.converter, converter)) { - result = result.withConverter(converter); - } - return result; - } - /** * Retrieves the common type of each field. + * * @return Common type of each field. */ - public Class getFieldType() { - return fieldType; + public Class getFieldType() { + return this.fieldType; } - + /** * Retrieves the type of the object we're modifying. + * * @return Type of the object. */ - public Class getTargetType() { - return targetType; + public Class getTargetType() { + return this.targetType; } - + /** * Retrieves the object we're currently modifying. + * * @return Object we're modifying. */ public Object getTarget() { - return target; + return this.target; } - + /** * Retrieve the number of readable types. + * * @return Readable types. */ public int size() { - return data.size(); + return this.accessors.size(); } - - /** - * Create a new structure modifier for the new field type. - * @param Type - * @param fieldType - common type of each field. - * @param filtered - list of fields after filtering the original modifier. - * @param defaults - list of default values after filtering the original. - * @return A new structure modifier. - */ - protected StructureModifier withFieldType( - Class fieldType, List filtered, Map defaults) { - return withFieldType(fieldType, filtered, defaults, null); - } - - /** - * Create a new structure modifier for the new field type. - * @param Type - * @param fieldType - common type of each field. - * @param filtered - list of fields after filtering the original modifier. - * @param defaults - list of default values after filtering the original. - * @param converter - the new converter, or NULL. - * @return A new structure modifier. - */ - protected StructureModifier withFieldType( - Class fieldType, List filtered, - Map defaults, EquivalentConverter converter) { - - StructureModifier result = new StructureModifier<>(); - result.initialize(targetType, fieldType, filtered, defaults, - converter, new ConcurrentHashMap<>(), - useStructureCompiler); - return result; - } - - /** - * Retrieves a structure modifier of the same type for a different object target. - * @param target - different target of the same type. - * @return Structure modifier with the new target. - */ - public StructureModifier withTarget(Object target) { - final StructureModifier copy = new StructureModifier<>(); - - // Create a new instance - copy.initialize(this); - copy.target = target; - return copy; - } - - /** - * Retrieves a structure modifier with the same type and target, but using a new object converter. - * @param converter - the object converter to use. - * @return Structure modifier with the new converter. - */ - @SuppressWarnings("unchecked") - private StructureModifier withConverter(EquivalentConverter converter) { - StructureModifier copy = withTarget(target); - - copy.setConverter(converter); - return copy; - } - - /** - * Set the current object converter. Should only be called during construction. - * @param converter - current object converter. - */ - protected void setConverter(EquivalentConverter converter) { - this.converter = converter; - } - + /** * Retrieves a list of the fields matching the constraints of this structure modifier. + * * @return List of fields. */ - public List getFields() { - return ImmutableList.copyOf(data); + public List getFields() { + return Collections.unmodifiableList(this.accessors); } /** * 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); + FieldAccessor accessor = this.findFieldAccessor(fieldIndex); + if (accessor == null) { + throw new IllegalArgumentException(String.format( + "Field index %d is out of bounds for length %s", + fieldIndex, + this.accessors.size())); + } + + return accessor.getField(); } - + /** * Retrieve every value stored in the fields of the current type. + * * @return Every field value. * @throws FieldAccessException Unable to access one or all of the fields */ - public List getValues() throws FieldAccessException { - final List values = new ArrayList<>(); - - for (int i = 0; i < size(); i++) { - values.add(read(i)); + public List getValues() throws FieldAccessException { + List values = new ArrayList<>(); + for (int i = 0; i < this.size(); i++) { + values.add(this.readSafely(i)); } - + return values; } - - // Used to generate plausible default values - private static Map generateDefaultFields(List fields) { - final Map requireDefaults = new HashMap<>(); - DefaultInstances generator = DEFAULT_GENERATOR; - int index = 0; - - for (Field field : fields) { - Class type = field.getType(); - int modifier = field.getModifiers(); - - // First, ignore primitive fields and final fields - if (!type.isPrimitive() && !Modifier.isFinal(modifier)) { - // Next, see if we actually can generate a default value - if (generator.getDefault(type) != null) { - // If so, require it - requireDefaults.put(field, index); + + /** + * Retrieves a structure modifier that only reads and writes fields of a given type. + * + * @param Type + * @param fieldType - the type, or supertype, of every field to modify. + * @return A structure modifier for fields of this type. + */ + public StructureModifier withType(Class fieldType) { + return this.withType(fieldType, null); + } + + /** + * Retrieves a structure modifier that only reads and writes fields of a given type. + * + * @param Type + * @param fieldType - the type, or supertype, of every field to modify. + * @param converter - converts objects into the given type. + * @return A structure modifier for fields of this type. + */ + public StructureModifier withType(Class fieldType, EquivalentConverter converter) { + return this.withParamType(fieldType, converter); + } + + /** + * Retrieves a structure modifier that only reads and writes fields of a given type. + * + * @param Type + * @param fieldType - the type, or supertype, of every field to modify. + * @param converter - converts objects into the given type. + * @param paramTypes - field type parameters + * @return A structure modifier for fields of this type. + */ + @SuppressWarnings("unchecked") + public StructureModifier withParamType( + Class fieldType, + EquivalentConverter converter, + Class... paramTypes + ) { + if (fieldType == null) { + // It's not supported in this version, so return an empty modifier + return (StructureModifier) NO_OP_MODIFIER; + } + + // Do we need to update the cache? + StructureModifier result = (StructureModifier) this.subtypeCache.get(fieldType); + if (result == null) { + List fields = new ArrayList<>(); + Map defaults = new HashMap<>(); + + // filter out all fields we don't need + for (int i = 0; i < this.accessors.size(); i++) { + FieldAccessor accessor = this.accessors.get(i); + Field field = accessor.getField(); + + // check if the field type matches + if (!fieldType.isAssignableFrom(field.getType())) { + continue; + } + + // check if we need to check for parameters + if (paramTypes.length > 0) { + // check if the field is parameterized + Type generic = field.getGenericType(); + if (!(generic instanceof ParameterizedType)) { + continue; + } + + // check if the type arguments of the field are matching + ParameterizedType parameterized = (ParameterizedType) generic; + if (!Arrays.equals(parameterized.getActualTypeArguments(), paramTypes)) { + continue; + } + } + + // this field should be included + fields.add(accessor); + if (this.defaultFields.containsKey(accessor)) { + defaults.put(accessor, i); } } - - // Increment field index - index++; - } - - return requireDefaults; - } - - // Used to filter out irrelevant fields - private static List getFields(Class type, Class superclassExclude) { - final List result = new ArrayList<>(); - - // Retrieve every private and public field - for (Field field : FuzzyReflection.fromClass(type, true).getDeclaredFields(superclassExclude)) { - int mod = field.getModifiers(); - - // Ignore static and "abstract packet" fields - if (!Modifier.isStatic(mod) && - (superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude) - )) { - - result.add(field); - } + + // Cache structure modifiers + result = this.withFieldType(fieldType, fields, defaults); + this.subtypeCache.put(fieldType, result); } + + // Add the target too + result = result.withTarget(this.target); + result.converter = converter; + return result; } + /** + * Create a new structure modifier for the new field type. + * + * @param Type + * @param fieldType - common type of each field. + * @param filtered - list of fields after filtering the original modifier. + * @param defaults - list of default values after filtering the original. + * @return A new structure modifier. + */ + protected StructureModifier withFieldType( + Class fieldType, + List filtered, + Map defaults + ) { + return this.withFieldType(fieldType, filtered, defaults, null); + } + + /** + * Create a new structure modifier for the new field type. + * + * @param Type + * @param fieldType - common type of each field. + * @param filtered - list of fields after filtering the original modifier. + * @param defaults - list of default values after filtering the original. + * @param converter - the new converter, or NULL. + * @return A new structure modifier. + */ + @SuppressWarnings("SameParameterValue") // api method, maybe someone needs it + protected StructureModifier withFieldType( + Class fieldType, + List filtered, + Map defaults, + EquivalentConverter converter + ) { + StructureModifier result = new StructureModifier<>(); + result.initialize( + this.targetType, + fieldType, + filtered, + defaults, + converter, + new HashMap<>()); + return result; + } + + /** + * Retrieves a structure modifier of the same type for a different object target. + * + * @param target - different target of the same type. + * @return Structure modifier with the new target. + */ + public StructureModifier withTarget(Object target) { + StructureModifier copy = new StructureModifier<>(); + + // Create a new instance + copy.initialize(this); + copy.target = target; + return copy; + } + + /** + * Retrieves a structure modifier with the same type and target, but using a new object converter. + * + * @param converter - the object converter to use. + * @return Structure modifier with the new converter. + */ + @SuppressWarnings("unchecked") + private StructureModifier withConverter(EquivalentConverter converter) { + StructureModifier copy = (StructureModifier) this.withTarget(this.target); + copy.setConverter(converter); + return copy; + } + + /** + * Set the current object converter. Should only be called during construction. + * + * @param converter - current object converter. + */ + protected void setConverter(EquivalentConverter converter) { + this.converter = converter; + } + @Override public String toString() { - return "StructureModifier[fieldType=" + fieldType + ", data=" + data + "]"; + return "StructureModifier[fieldType=" + this.fieldType + ", data=" + this.accessors + "]"; } } diff --git a/src/main/java/com/comphenix/protocol/reflect/VolatileField.java b/src/main/java/com/comphenix/protocol/reflect/VolatileField.java deleted file mode 100644 index 12185722..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/VolatileField.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.reflect; - -import java.lang.reflect.Field; - -import com.comphenix.protocol.ProtocolLogger; -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import com.google.common.base.Objects; - -/** - * Represents a field that will revert to its original state when this class is garbaged collected. - * - * @author Kristian - */ -public class VolatileField { - private FieldAccessor accessor; - private Object container; - - // The current and previous values - private Object previous; - private Object current; - - // Whether or not we must reset or load - private boolean previousLoaded; - private boolean currentSet; - - // Whether or not to break access restrictions - private boolean forceAccess; - - /** - * Initializes a volatile field with an associated object. - * @param field - the field. - * @param container - the object this field belongs to. - */ - public VolatileField(Field field, Object container) { - this.accessor = Accessors.getFieldAccessor(field); - this.container = container; - } - - /** - * Initializes a volatile field with an associated object. - * @param field - the field. - * @param container - the object this field belongs to. - * @param forceAccess - whether or not to override any scope restrictions. - */ - public VolatileField(Field field, Object container, boolean forceAccess) { - this.accessor = Accessors.getFieldAccessor(field, true); - this.container = container; - this.forceAccess = forceAccess; - } - - /** - * Initializes a volatile field with the given accessor and associated object. - * @param accessor - the field accessor. - * @param container - the object this field belongs to. - */ - public VolatileField(FieldAccessor accessor, Object container) { - this.accessor = accessor; - this.container = container; - } - - /** - * Retrieves the current field. - * @return The stored field. - */ - public Field getField() { - return accessor.getField(); - } - - /** - * Retrieves the object the field is stored. - * @return The reference object. - */ - public Object getContainer() { - return container; - } - - /** - * Retrieves whether or not not to override any scope restrictions. - * @return TRUE if we override scope, FALSE otherwise. - */ - public boolean isForceAccess() { - return forceAccess; - } - - /** - * Sets whether or not not to override any scope restrictions. - * @param forceAccess - TRUE if we override scope, FALSE otherwise. - */ - public void setForceAccess(boolean forceAccess) { - this.forceAccess = forceAccess; - } - - /** - * Retrieves the current field value. - * @return The current field value. - */ - public Object getValue() { - // Retrieve the correct value - if (!currentSet) { - ensureLoaded(); - return previous; - } else { - return current; - } - } - - /** - * Retrieves the field value before the previous setValue(), unless saveValue() has been called. - * @return Previous value. - */ - public Object getOldValue() { - ensureLoaded(); - return previous; - } - - /** - * Sets the current value. This will be reverted unless saveValue() is called. - * @param newValue - new field value. - */ - public void setValue(Object newValue) { - // Remember to safe the previous value - ensureLoaded(); - - writeFieldValue(newValue); - current = newValue; - currentSet = true; - } - - /** - * Reapply the current changed value. - *

    - * Also refresh the previously set value. - */ - public void refreshValue() { - Object fieldValue = readFieldValue(); - - if (currentSet) { - // If they differ, we need to set them again - if (!Objects.equal(current, fieldValue)) { - previous = readFieldValue(); - previousLoaded = true; - writeFieldValue(current); - } - } else if (previousLoaded) { - // Update that too - previous = fieldValue; - } - } - - /** - * Ensure that the current value is still set after this class has been garbaged collected. - */ - public void saveValue() { - previous = current; - currentSet = false; - } - - /** - * Revert to the previously set value. - */ - public void revertValue() { - // Reset value if it hasn't been changed by anyone else - if (currentSet) { - if (getValue() == current) { - setValue(previous); - currentSet = false; - } else { - // This can be a bad sign - ProtocolLogger.log("Unable to switch {0} to {1}. Expected {2}, but got {3}.", getField().toGenericString(), previous, current, getValue()); - } - } - } - - /** - * Retrieve a synchronized version of the current field. - * @return A synchronized volatile field. - */ - public VolatileField toSynchronized() { - return new VolatileField(Accessors.getSynchronized(accessor), container); - } - - /** - * Determine whether or not we'll need to revert the value. - * @return True if it is set, false if not. - */ - public boolean isCurrentSet() { - return currentSet; - } - - private void ensureLoaded() { - // Load the value if we haven't already - if (!previousLoaded) { - previous = readFieldValue(); - previousLoaded = true; - } - } - - /** - * Read the content of the underlying field. - * @return The field value. - */ - private Object readFieldValue() { - return accessor.get(container); - } - - /** - * Write the given value to the underlying field. - * @param newValue - the new value. - */ - private void writeFieldValue(Object newValue) { - accessor.set(container, newValue); - } - - @Override - protected void finalize() throws Throwable { - revertValue(); - } - - @Override - public String toString() { - return "VolatileField [accessor=" + accessor + ", container=" + container + ", previous=" - + previous + ", current=" + current + ", previousLoaded=" + previousLoaded - + ", currentSet=" + currentSet + ", forceAccess=" + forceAccess + "]"; - } -} diff --git a/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java b/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java index 059ae17d..7ab6660e 100644 --- a/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java +++ b/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java @@ -1,54 +1,50 @@ package com.comphenix.protocol.reflect.accessors; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.List; - import com.comphenix.protocol.reflect.ExactReflection; import com.comphenix.protocol.reflect.FuzzyReflection; -import com.google.common.base.Joiner; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; public final class Accessors { - /** - * Represents a field accessor that synchronizes access to the underlying field. - * @author Kristian - */ - public static final class SynchronizedFieldAccessor implements FieldAccessor { - private final FieldAccessor accessor; - private SynchronizedFieldAccessor(FieldAccessor accessor) { - this.accessor = accessor; - } - - @Override - public void set(Object instance, Object value) { - Object lock = accessor.get(instance); - - if (lock != null) { - synchronized (lock) { - accessor.set(instance, value); - } - } else { - accessor.set(instance, value); - } - } - - @Override - public Object get(Object instance) { - return accessor.get(instance); - } - - @Override - public Field getField() { - return accessor.getField(); - } + + // Seal this class + private Accessors() { } - + + /** + * Retrieve an accessor (in declared order) for every field of the givne type. + * + * @param clazz - the type of the instance to retrieve. + * @param fieldClass - type of the field(s) to retrieve. + * @param forceAccess - whether to look for private and protected fields. + * @return The accessors. + */ + public static FieldAccessor[] getFieldAccessorArray(Class clazz, Class fieldClass, boolean forceAccess) { + return FuzzyReflection.fromClass(clazz, forceAccess).getFieldListByType(fieldClass).stream() + .map(Accessors::getFieldAccessor) + .toArray(FieldAccessor[]::new); + } + /** * Retrieve an accessor for the first field of the given type. + * * @param instanceClass - the type of the instance to retrieve. - * @param fieldClass - type of the field to retrieve. - * @param forceAccess - whether or not to look for private and protected fields. + * @param fieldName - name of the field to retrieve. + * @param forceAccess - whether to look for private and protected fields. + * @return The value of that field. + * @throws IllegalArgumentException If the field cannot be found. + */ + //public static FieldAccessor getFieldAccessor(Class instanceClass, String fieldName, boolean forceAccess) { + // return Accessors.getFieldAccessor(ExactReflection.fromClass(instanceClass, forceAccess).getField(fieldName)); + //} + + /** + * Retrieve an accessor for the first field of the given type. + * + * @param instanceClass - the type of the instance to retrieve. + * @param fieldClass - type of the field to retrieve. + * @param forceAccess - whether to look for private and protected fields. * @return The field accessor. * @throws IllegalArgumentException If the field cannot be found. */ @@ -58,242 +54,114 @@ public final class Accessors { return Accessors.getFieldAccessor(field); } - /** - * Retrieve an accessor (in declared order) for every field of the givne type. - * @param instanceClass - the type of the instance to retrieve. - * @param fieldClass - type of the field(s) to retrieve. - * @param forceAccess - whether or not to look for private and protected fields. - * @return The accessors. - */ - public static FieldAccessor[] getFieldAccessorArray(Class instanceClass, Class fieldClass, boolean forceAccess) { - List fields = FuzzyReflection.fromClass(instanceClass, forceAccess).getFieldListByType(fieldClass); - FieldAccessor[] accessors = new FieldAccessor[fields.size()]; - - for (int i = 0; i < accessors.length; i++) { - accessors[i] = getFieldAccessor(fields.get(i)); - } - return accessors; - } - - /** - * Retrieve an accessor for the first field of the given type. - * @param instanceClass - the type of the instance to retrieve. - * @param fieldName - name of the field to retrieve. - * @param forceAccess - whether or not to look for private and protected fields. - * @return The value of that field. - * @throws IllegalArgumentException If the field cannot be found. - */ - public static FieldAccessor getFieldAccessor(Class instanceClass, String fieldName, boolean forceAccess) { - return Accessors.getFieldAccessor(ExactReflection.fromClass(instanceClass, true).getField(fieldName)); - } - - /** - * Retrieve a field accessor from a given field that uses unchecked exceptions. - * @param field - the field. - * @return The field accessor. - */ - public static FieldAccessor getFieldAccessor(final Field field) { - return Accessors.getFieldAccessor(field, true); - } - - /** - * Retrieve a field accessor from a given field that uses unchecked exceptions. - * @param field - the field. - * @param forceAccess - whether or not to skip Java access checking. - * @return The field accessor. - */ - public static FieldAccessor getFieldAccessor(final Field field, boolean forceAccess) { - field.setAccessible(true); - return new DefaultFieldAccessor(field); - } - /** * Retrieve a field accessor for a field with the given name and equivalent type, or NULL. - * @param clazz - the declaration class. + * + * @param clazz - the declaration class. * @param fieldName - the field name. * @param fieldType - assignable field type. * @return The field accessor, or NULL if not found. */ - public static FieldAccessor getFieldAcccessorOrNull(Class clazz, String fieldName, Class fieldType) { - try { - FieldAccessor accessor = Accessors.getFieldAccessor(clazz, fieldName, true); - - // Verify the type - if (fieldType.isAssignableFrom(accessor.getField().getType())) { - return accessor; - } - return null; - } catch (IllegalArgumentException e) { - return null; - } + public static FieldAccessor getFieldAccessorOrNull(Class clazz, String fieldName, Class fieldType) { + Field field = ExactReflection.fromClass(clazz, true).findField(fieldName); + if (field != null && (fieldType == null || fieldType.isAssignableFrom(field.getType()))) { + return Accessors.getFieldAccessor(field); + } + + // no matching field found + return null; + } + + /** + * Retrieve a field accessor from a given field that uses unchecked exceptions. + * + * @param field - the field. + * @return The field accessor. + */ + public static FieldAccessor getFieldAccessor(Field field) { + return MethodHandleHelper.getFieldAccessor(field); + } + + /** + * Retrieve a field accessor that will cache the content of the field. + *

    + * Note that we don't check if the underlying field has changed after the value has been cached, so it's best to use + * this on final fields. + * + * @param inner - the accessor. + * @return A cached field accessor. + */ + public static FieldAccessor getMemorizing(FieldAccessor inner) { + return new MemorizingFieldAccessor(inner); + } + + /** + * Retrieve a method accessor for a method with the given name and signature. + * + * @param instanceClass - the parent class. + * @param methodName - the method name. + * @param parameters - the parameters. + * @return The method accessor. + */ + public static MethodAccessor getMethodAccessor(Class instanceClass, String methodName, Class... parameters) { + Method method = ExactReflection.fromClass(instanceClass, true).getMethod(methodName, parameters); + return Accessors.getMethodAccessor(method); } /** * Retrieve a method accessor for a field with the given name and equivalent type, or NULL. - * @param clazz - the declaration class. + * + * @param clazz - the declaration class. * @param methodName - the method name. * @return The method accessor, or NULL if not found. */ - public static MethodAccessor getMethodAcccessorOrNull(Class clazz, String methodName) { - try { - return Accessors.getMethodAccessor(clazz, methodName); - } catch (IllegalArgumentException e) { - return null; - } - } - - /** - * Find a specific constructor in a class. - * @param clazz - the class. - * @param parameters - the signature of the constructor to find. - * @return The constructor, or NULL if not found. - */ - public static ConstructorAccessor getConstructorAccessorOrNull(Class clazz, Class... parameters) { - try { - return Accessors.getConstructorAccessor(clazz, parameters); - } catch (IllegalArgumentException e) { - return null; // Not found - } - } - - /** - * Retrieve a field accessor that will cache the content of the field. - *

    - * Note that we don't check if the underlying field has changed after the value has been cached, - * so it's best to use this on final fields. - * @param inner - the accessor. - * @return A cached field accessor. - */ - public static FieldAccessor getCached(final FieldAccessor inner) { - return new FieldAccessor() { - private final Object EMPTY = new Object(); - private volatile Object value = EMPTY; - - @Override - public void set(Object instance, Object value) { - inner.set(instance, value); - update(value); - } - - @Override - public Object get(Object instance) { - Object cache = value; - - if (cache != EMPTY) - return cache; - return update(inner.get(instance)); - } - - /** - * Update the cached value. - * @param value - the value to cache. - * @return The cached value. - */ - private Object update(Object value) { - return this.value = value; - } - - @Override - public Field getField() { - return inner.getField(); - } - }; - } - - /** - * Retrieve a field accessor where the write operation is synchronized on the current field value. - * @param accessor - the accessor. - * @return The field accessor. - */ - public static FieldAccessor getSynchronized(final FieldAccessor accessor) { - // Only wrap once - if (accessor instanceof SynchronizedFieldAccessor) - return accessor; - return new SynchronizedFieldAccessor(accessor); - } - - /** - * Retrieve a method accessor that always return a constant value, regardless if input. - * @param returnValue - the constant return value. - * @param method - the method. - * @return A constant method accessor. - */ - public static MethodAccessor getConstantAccessor(final Object returnValue, final Method method) { - return new MethodAccessor() { - @Override - public Object invoke(Object target, Object... args) { - return returnValue; - } - - @Override - public Method getMethod() { - return method; - } - }; - } - - /** - * Retrieve a method accessor for a method with the given name and signature. - * @param instanceClass - the parent class. - * @param methodName - the method name. - * @param parameters - the parameters. - * @return The method accessor. - */ - public static MethodAccessor getMethodAccessor(Class instanceClass, String methodName, Class... parameters) { - return new DefaultMethodAccessor(ExactReflection.fromClass(instanceClass, true).getMethod(methodName, parameters)); + public static MethodAccessor getMethodAccessorOrNull(Class clazz, String methodName, Class... parameters) { + Method method = ExactReflection.fromClass(clazz, true).findMethod(methodName, parameters); + return method == null ? null : Accessors.getMethodAccessor(method); } /** * Retrieve a method accessor for a particular method, avoding checked exceptions. + * * @param method - the method to access. * @return The method accessor. */ - public static MethodAccessor getMethodAccessor(final Method method) { - return getMethodAccessor(method, true); - } - - /** - * Retrieve a method accessor for a particular method, avoding checked exceptions. - * @param method - the method to access. - * @param forceAccess - whether or not to skip Java access checking. - * @return The method accessor. - */ - public static MethodAccessor getMethodAccessor(final Method method, boolean forceAccess) { - method.setAccessible(forceAccess); - return new DefaultMethodAccessor(method); + public static MethodAccessor getMethodAccessor(Method method) { + return MethodHandleHelper.getMethodAccessor(method); } /** * Retrieve a constructor accessor for a constructor with the given signature. + * * @param instanceClass - the parent class. - * @param parameters - the parameters. + * @param parameters - the parameters. * @return The constructor accessor. * @throws IllegalArgumentException If we cannot find this constructor. - * @throws IllegalStateException If we cannot access reflection. */ public static ConstructorAccessor getConstructorAccessor(Class instanceClass, Class... parameters) { - try { - return getConstructorAccessor(instanceClass.getDeclaredConstructor(parameters)); - } catch (NoSuchMethodException e) { - throw new IllegalArgumentException(String.format( - "Unable to find constructor %s(%s).", instanceClass, Joiner.on(",").join(parameters)) - ); - } catch (SecurityException e) { - throw new IllegalStateException("Cannot access constructors.", e); - } + Constructor constructor = ExactReflection.fromClass(instanceClass, true).findConstructor(parameters); + return Accessors.getConstructorAccessor(constructor); + } + + /** + * Find a specific constructor in a class. + * + * @param clazz - the class. + * @param parameters - the signature of the constructor to find. + * @return The constructor, or NULL if not found. + */ + public static ConstructorAccessor getConstructorAccessorOrNull(Class clazz, Class... parameters) { + Constructor constructor = ExactReflection.fromClass(clazz, true).findConstructor(parameters); + return constructor == null ? null : Accessors.getConstructorAccessor(constructor); } /** * Retrieve a constructor accessor for a particular constructor, avoding checked exceptions. + * * @param constructor - the constructor to access. * @return The method accessor. */ public static ConstructorAccessor getConstructorAccessor(final Constructor constructor) { - constructor.setAccessible(true); // let us in even if we are not allowed to - return new DefaultConstrutorAccessor(constructor); - } - - // Seal this class - private Accessors() { + return MethodHandleHelper.getConstructorAccessor(constructor); } } diff --git a/src/main/java/com/comphenix/protocol/reflect/accessors/ConstructorAccessor.java b/src/main/java/com/comphenix/protocol/reflect/accessors/ConstructorAccessor.java index 15c5b3b1..c2562749 100644 --- a/src/main/java/com/comphenix/protocol/reflect/accessors/ConstructorAccessor.java +++ b/src/main/java/com/comphenix/protocol/reflect/accessors/ConstructorAccessor.java @@ -3,16 +3,19 @@ package com.comphenix.protocol.reflect.accessors; import java.lang.reflect.Constructor; public interface ConstructorAccessor { + /** * Invoke the underlying constructor. + * * @param args - the arguments to pass to the method. * @return The return value, or NULL for void methods. */ - public Object invoke(Object... args); - + Object invoke(Object... args); + /** * Retrieve the underlying constructor. + * * @return The method. */ - public Constructor getConstructor(); + Constructor getConstructor(); } diff --git a/src/main/java/com/comphenix/protocol/reflect/accessors/DefaultConstrutorAccessor.java b/src/main/java/com/comphenix/protocol/reflect/accessors/DefaultConstrutorAccessor.java index c9471ce9..3a72f2ce 100644 --- a/src/main/java/com/comphenix/protocol/reflect/accessors/DefaultConstrutorAccessor.java +++ b/src/main/java/com/comphenix/protocol/reflect/accessors/DefaultConstrutorAccessor.java @@ -1,54 +1,29 @@ package com.comphenix.protocol.reflect.accessors; +import java.lang.invoke.MethodHandle; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; final class DefaultConstrutorAccessor implements ConstructorAccessor { + private final Constructor constructor; - - public DefaultConstrutorAccessor(Constructor method) { - this.constructor = method; + private final MethodHandle constructorAccessor; + + public DefaultConstrutorAccessor(Constructor constructor, MethodHandle constructorAccessor) { + this.constructor = constructor; + this.constructorAccessor = constructorAccessor; } - + @Override public Object invoke(Object... args) { try { - return constructor.newInstance(args); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Cannot use reflection.", e); - } catch (IllegalArgumentException e) { - throw e; - } catch (InvocationTargetException e) { - throw new RuntimeException("An internal error occured.", e.getCause()); - } catch (InstantiationException e) { - throw new RuntimeException("Cannot instantiate object.", e); + return this.constructorAccessor.invokeExact(args); + } catch (Throwable throwable) { + throw new IllegalStateException("Unable to construct new instance using " + this.constructor, throwable); } } - + @Override public Constructor getConstructor() { - return constructor; - } - - @Override - public int hashCode() { - return constructor != null ? constructor.hashCode() : 0; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - - if (obj instanceof DefaultConstrutorAccessor) { - DefaultConstrutorAccessor other = (DefaultConstrutorAccessor) obj; - return other.constructor == constructor; - } - return true; - } - - @Override - public String toString() { - return "DefaultConstrutorAccessor [constructor=" + constructor + "]"; + return this.constructor; } } diff --git a/src/main/java/com/comphenix/protocol/reflect/accessors/DefaultFieldAccessor.java b/src/main/java/com/comphenix/protocol/reflect/accessors/DefaultFieldAccessor.java index 08c4a788..8314f8ca 100644 --- a/src/main/java/com/comphenix/protocol/reflect/accessors/DefaultFieldAccessor.java +++ b/src/main/java/com/comphenix/protocol/reflect/accessors/DefaultFieldAccessor.java @@ -1,82 +1,50 @@ package com.comphenix.protocol.reflect.accessors; -import com.comphenix.protocol.ProtocolLogger; import java.lang.invoke.MethodHandle; -import java.lang.invoke.WrongMethodTypeException; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; final class DefaultFieldAccessor implements FieldAccessor { private final Field field; + private final boolean staticField; - private MethodHandle setter; + private final MethodHandle setter; + private final MethodHandle getter; - public DefaultFieldAccessor(Field field) { + public DefaultFieldAccessor(Field field, MethodHandle setter, MethodHandle getter, boolean staticField) { this.field = field; - // try to get a getter and setter handle for the field - if (UnsafeFieldAccess.hasTrustedLookup()) { - try { - setter = UnsafeFieldAccess.findSetter(field); - } catch (ReflectiveOperationException exception) { - ProtocolLogger.debug("Unable to get setter for field " + field, exception); - } - } + this.setter = setter; + this.getter = getter; + this.staticField = staticField; } @Override public Object get(Object instance) { try { - return field.get(instance); - } catch (IllegalArgumentException e) { - throw new RuntimeException("Cannot read " + field, e); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Cannot use reflection.", e); + // we need this check to as the handle will treat "null" as an instance too + return this.staticField ? this.getter.invokeExact() : this.getter.invokeExact(instance); + } catch (Throwable throwable) { + throw new IllegalStateException("Unable to read field value of " + this.field, throwable); } } @Override public void set(Object instance, Object value) { try { - if (setter == null) { - field.set(instance, value); + // we need this check to as the handle will treat "null" as an instance too + if (this.staticField) { + this.setter.invokeExact(value); } else { - setter.invoke(instance, value); + this.setter.invokeExact(instance, value); } - } catch (IllegalArgumentException | ClassCastException e) { - throw new RuntimeException("Cannot set field " + field + " to value " + value, e); - } catch (IllegalAccessException | WrongMethodTypeException e) { - throw new IllegalStateException("Cannot use reflection.", e); - } catch (Throwable ignored) { - // cannot happen - this might only occur when the handle targets a method - throw new RuntimeException("Cannot happen"); + } catch (Throwable throwable) { + throw new IllegalStateException("Unable to set value of field " + this.field, throwable); } } @Override public Field getField() { - return field; - } - - @Override - public int hashCode() { - return field != null ? field.hashCode() : 0; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (obj instanceof DefaultFieldAccessor) { - DefaultFieldAccessor other = (DefaultFieldAccessor) obj; - return other.field == field; - } - return true; - } - - @Override - public String toString() { - return "DefaultFieldAccessor [field=" + field + "]"; + return this.field; } } diff --git a/src/main/java/com/comphenix/protocol/reflect/accessors/DefaultMethodAccessor.java b/src/main/java/com/comphenix/protocol/reflect/accessors/DefaultMethodAccessor.java index 4c5d1e7d..2a0ccf9f 100644 --- a/src/main/java/com/comphenix/protocol/reflect/accessors/DefaultMethodAccessor.java +++ b/src/main/java/com/comphenix/protocol/reflect/accessors/DefaultMethodAccessor.java @@ -1,52 +1,33 @@ package com.comphenix.protocol.reflect.accessors; -import java.lang.reflect.InvocationTargetException; +import java.lang.invoke.MethodHandle; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; final class DefaultMethodAccessor implements MethodAccessor { + private final Method method; - - public DefaultMethodAccessor(Method method) { + private final boolean staticMethod; + + private final MethodHandle methodHandle; + + public DefaultMethodAccessor(Method method, MethodHandle methodHandle, boolean staticMethod) { this.method = method; + this.methodHandle = methodHandle; + this.staticMethod = staticMethod; } - + @Override public Object invoke(Object target, Object... args) { try { - return method.invoke(target, args); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Cannot use reflection.", e); - } catch (InvocationTargetException e) { - throw new RuntimeException("An internal error occured.", e.getCause()); - } catch (IllegalArgumentException e) { - throw e; + return this.methodHandle.invoke(target, args); + } catch (Throwable throwable) { + throw new IllegalStateException("Unable to invoke method " + this.method, throwable); } } - + @Override public Method getMethod() { - return method; - } - - @Override - public int hashCode() { - return method != null ? method.hashCode() : 0; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - - if (obj instanceof DefaultMethodAccessor) { - DefaultMethodAccessor other = (DefaultMethodAccessor) obj; - return other.method == method; - } - return true; - } - - @Override - public String toString() { - return "DefaultMethodAccessor [method=" + method + "]"; + return this.method; } } diff --git a/src/main/java/com/comphenix/protocol/reflect/accessors/FieldAccessor.java b/src/main/java/com/comphenix/protocol/reflect/accessors/FieldAccessor.java index bdd9a43c..c4692aa4 100644 --- a/src/main/java/com/comphenix/protocol/reflect/accessors/FieldAccessor.java +++ b/src/main/java/com/comphenix/protocol/reflect/accessors/FieldAccessor.java @@ -4,27 +4,32 @@ import java.lang.reflect.Field; /** * Represents an interface for accessing a field. + * * @author Kristian */ public interface FieldAccessor { + /** * Retrieve the value of a field for a particular instance. + * * @param instance - the instance, or NULL for a static field. * @return The value of the field. * @throws IllegalStateException If the current security context prohibits reflection. */ - public Object get(Object instance); - + Object get(Object instance); + /** * Set the value of a field for a particular instance. + * * @param instance - the instance, or NULL for a static field. - * @param value - the new value of the field. + * @param value - the new value of the field. */ - public void set(Object instance, Object value); - + void set(Object instance, Object value); + /** * Retrieve the underlying field. + * * @return The field. */ - public Field getField(); + Field getField(); } diff --git a/src/main/java/com/comphenix/protocol/reflect/accessors/MemorizingFieldAccessor.java b/src/main/java/com/comphenix/protocol/reflect/accessors/MemorizingFieldAccessor.java new file mode 100644 index 00000000..fb872a0e --- /dev/null +++ b/src/main/java/com/comphenix/protocol/reflect/accessors/MemorizingFieldAccessor.java @@ -0,0 +1,36 @@ +package com.comphenix.protocol.reflect.accessors; + +import java.lang.reflect.Field; + +final class MemorizingFieldAccessor implements FieldAccessor { + + // a marker object which indicates the value of the field wasn't yet read + private static final Object NIL = new Object(); + + private final FieldAccessor inner; + private volatile Object fieldValue = NIL; + + public MemorizingFieldAccessor(FieldAccessor inner) { + this.inner = inner; + } + + @Override + public Object get(Object instance) { + if (this.fieldValue == NIL) { + this.fieldValue = this.inner.get(instance); + } + + return this.fieldValue; + } + + @Override + public void set(Object instance, Object value) { + this.inner.set(instance, value); + this.fieldValue = value; + } + + @Override + public Field getField() { + return this.inner.getField(); + } +} diff --git a/src/main/java/com/comphenix/protocol/reflect/accessors/MethodAccessor.java b/src/main/java/com/comphenix/protocol/reflect/accessors/MethodAccessor.java index 04979927..d5a460fc 100644 --- a/src/main/java/com/comphenix/protocol/reflect/accessors/MethodAccessor.java +++ b/src/main/java/com/comphenix/protocol/reflect/accessors/MethodAccessor.java @@ -4,20 +4,24 @@ import java.lang.reflect.Method; /** * Represents an interface for invoking a method. - * @author Kristian + * + * @author Kristian */ public interface MethodAccessor { + /** * Invoke the underlying method. + * * @param target - the target instance, or NULL for a static method. - * @param args - the arguments to pass to the method. + * @param args - the arguments to pass to the method. * @return The return value, or NULL for void methods. */ - public Object invoke(Object target, Object... args); - + Object invoke(Object target, Object... args); + /** * Retrieve the underlying method. + * * @return The method. */ - public Method getMethod(); + Method getMethod(); } diff --git a/src/main/java/com/comphenix/protocol/reflect/accessors/MethodHandleHelper.java b/src/main/java/com/comphenix/protocol/reflect/accessors/MethodHandleHelper.java new file mode 100644 index 00000000..733a2c6a --- /dev/null +++ b/src/main/java/com/comphenix/protocol/reflect/accessors/MethodHandleHelper.java @@ -0,0 +1,121 @@ +package com.comphenix.protocol.reflect.accessors; + +import com.comphenix.protocol.ProtocolLogger; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.logging.Level; + +final class MethodHandleHelper { + + private static final Lookup LOOKUP; + + // static fields, converted as "public Object get()" and "public void set(Object value)" + private static final MethodType STATIC_FIELD_GETTER = MethodType.methodType(Object.class); + private static final MethodType STATIC_FIELD_SETTER = MethodType.methodType(void.class, Object.class); + // instance fields, converted as "public Object get(Object instance)" and "public void set(Object instance, Object value)" + private static final MethodType VIRTUAL_FIELD_GETTER = MethodType.methodType(Object.class, Object.class); + private static final MethodType VIRTUAL_FIELD_SETTER = MethodType.methodType(void.class, Object.class, Object.class); + + static { + Lookup lookup; + try { + // get the unsafe class + Class unsafeClass = Class.forName("sun.misc.Unsafe"); + // get the unsafe instance + Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + sun.misc.Unsafe unsafe = (sun.misc.Unsafe) theUnsafe.get(null); + // get the trusted lookup field + Field trustedLookup = Lookup.class.getDeclaredField("IMPL_LOOKUP"); + // get access to the base and offset value of it + long offset = unsafe.staticFieldOffset(trustedLookup); + Object baseValue = unsafe.staticFieldBase(trustedLookup); + // get the trusted lookup instance + lookup = (Lookup) unsafe.getObject(baseValue, offset); + } catch (Exception exception) { + ProtocolLogger.log(Level.SEVERE, "Unable to retrieve trusted lookup", exception); + lookup = MethodHandles.lookup(); + } + + LOOKUP = lookup; + } + + // sealed class + private MethodHandleHelper() { + } + + public static MethodAccessor getMethodAccessor(Method method) { + try { + MethodHandle unreflected = LOOKUP.unreflect(method); + boolean staticMethod = Modifier.isStatic(method.getModifiers()); + + MethodHandle generified = convertToGeneric(unreflected, staticMethod, false); + return new DefaultMethodAccessor(method, generified, staticMethod); + } catch (IllegalAccessException exception) { + throw new IllegalStateException("Unable to access method " + method); + } + } + + public static ConstructorAccessor getConstructorAccessor(Constructor constructor) { + try { + MethodHandle unreflected = LOOKUP.unreflectConstructor(constructor); + MethodHandle generified = convertToGeneric(unreflected, false, true); + + return new DefaultConstrutorAccessor(constructor, generified); + } catch (IllegalAccessException exception) { + throw new IllegalStateException("Unable to access constructor " + constructor); + } + } + + public static FieldAccessor getFieldAccessor(Field field) { + try { + boolean staticField = Modifier.isStatic(field.getModifiers()); + + // java hates us - unreflecting a trusted field always results in an exception, finding them doesn't... + MethodHandle getter; + MethodHandle setter; + if (staticField) { + getter = LOOKUP.findStaticGetter(field.getDeclaringClass(), field.getName(), field.getType()); + setter = LOOKUP.findStaticSetter(field.getDeclaringClass(), field.getName(), field.getType()); + } else { + getter = LOOKUP.findGetter(field.getDeclaringClass(), field.getName(), field.getType()); + setter = LOOKUP.findSetter(field.getDeclaringClass(), field.getName(), field.getType()); + } + + // generify the method type so that we don't need to worry about it when using the handles + if (staticField) { + getter = getter.asType(STATIC_FIELD_GETTER); + setter = setter.asType(STATIC_FIELD_SETTER); + } else { + getter = getter.asType(VIRTUAL_FIELD_GETTER); + setter = setter.asType(VIRTUAL_FIELD_SETTER); + } + + return new DefaultFieldAccessor(field, setter, getter, staticField); + } catch (IllegalAccessException | NoSuchFieldException exception) { + // NoSuchFieldException can never happen, the field always exists + throw new IllegalStateException("Unable to access field " + field); + } + } + + private static MethodHandle convertToGeneric(MethodHandle handle, boolean staticMethod, boolean ctor) { + MethodHandle target = handle.asFixedArity(); + // special thing - we do not need the trailing array if we have 0 arguments anyway + int paramCount = handle.type().parameterCount() - (ctor || staticMethod ? 0 : 1); + MethodType methodType = MethodType.genericMethodType(ctor ? 0 : 1, true); + // spread the arguments we give into the handle + target = target.asSpreader(Object[].class, paramCount); + // adds a leading 'this' argument which we can ignore + if (staticMethod) { + target = MethodHandles.dropArguments(target, 0, Object.class); + } + // convert the type to finish + return target.asType(methodType); + } +} diff --git a/src/main/java/com/comphenix/protocol/reflect/accessors/ReadOnlyFieldAccessor.java b/src/main/java/com/comphenix/protocol/reflect/accessors/ReadOnlyFieldAccessor.java deleted file mode 100644 index 1254d7a3..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/accessors/ReadOnlyFieldAccessor.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.comphenix.protocol.reflect.accessors; - -public abstract class ReadOnlyFieldAccessor implements FieldAccessor { - @Override - public final void set(Object instance, Object value) { - throw new UnsupportedOperationException("Cannot update the content of a read-only field accessor."); - } -} diff --git a/src/main/java/com/comphenix/protocol/reflect/accessors/UnsafeFieldAccess.java b/src/main/java/com/comphenix/protocol/reflect/accessors/UnsafeFieldAccess.java deleted file mode 100644 index 45c4e6a4..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/accessors/UnsafeFieldAccess.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.comphenix.protocol.reflect.accessors; - -import com.comphenix.protocol.ProtocolLogger; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles.Lookup; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.logging.Level; - -final class UnsafeFieldAccess { - - private static final Lookup TRUSTED_LOOKUP; - - static { - Lookup trusted = null; - try { - // get the unsafe class - Class unsafeClass = Class.forName("sun.misc.Unsafe"); - // get the unsafe instance - Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe"); - theUnsafe.setAccessible(true); - sun.misc.Unsafe unsafe = (sun.misc.Unsafe) theUnsafe.get(null); - // get the trusted lookup field - Field trustedLookup = Lookup.class.getDeclaredField("IMPL_LOOKUP"); - // get access to the base and offset value of it - long offset = unsafe.staticFieldOffset(trustedLookup); - Object baseValue = unsafe.staticFieldBase(trustedLookup); - // get the trusted lookup instance - trusted = (Lookup) unsafe.getObject(baseValue, offset); - } catch (Exception exception) { - ProtocolLogger.log(Level.SEVERE, "Unable to retrieve trusted lookup", exception); - } - - TRUSTED_LOOKUP = trusted; - } - - public static boolean hasTrustedLookup() { - return TRUSTED_LOOKUP != null; - } - - public static MethodHandle findSetter(Field field) throws ReflectiveOperationException { - if (Modifier.isStatic(field.getModifiers())) { - return TRUSTED_LOOKUP.findStaticSetter(field.getDeclaringClass(), field.getName(), field.getType()); - } else { - return TRUSTED_LOOKUP.findSetter(field.getDeclaringClass(), field.getName(), field.getType()); - } - } -} diff --git a/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java b/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java index 55155976..5ef82767 100644 --- a/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java +++ b/src/main/java/com/comphenix/protocol/reflect/cloning/BukkitCloner.java @@ -36,7 +36,7 @@ import net.md_5.bungee.api.chat.BaseComponent; /** * Represents an object that can clone a specific list of Bukkit- and Minecraft-related objects. - * + * * @author Kristian */ public class BukkitCloner implements Cloner { @@ -75,7 +75,6 @@ public class BukkitCloner implements Cloner { MinecraftReflection.getMinecraftItemStack(MinecraftReflection.getBukkitItemStack(source).clone())); fromWrapper(MinecraftReflection::getDataWatcherClass, WrappedDataWatcher::new); fromConverter(MinecraftReflection::getBlockPositionClass, BlockPosition.getConverter()); - fromConverter(MinecraftReflection::getChunkPositionClass, ChunkPosition.getConverter()); fromWrapper(MinecraftReflection::getServerPingClass, WrappedServerPing::fromHandle); fromConverter(MinecraftReflection::getMinecraftKeyClass, MinecraftKey.getConverter()); fromWrapper(MinecraftReflection::getIBlockDataClass, WrappedBlockData::fromHandle); @@ -133,7 +132,7 @@ public class BukkitCloner implements Cloner { @Override public Object clone(Object source) { - StructureModifier modifier = new StructureModifier<>(source.getClass(), true).withTarget(source); + StructureModifier modifier = new StructureModifier<>(source.getClass()).withTarget(source); List list = (List) modifier.read(0); Object empty = modifier.read(1); diff --git a/src/main/java/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java b/src/main/java/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java deleted file mode 100644 index 9ed511df..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.reflect.compiler; - -import java.lang.management.ManagementFactory; -import java.lang.management.MemoryPoolMXBean; -import java.lang.management.MemoryUsage; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; - -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.reflect.StructureModifier; -import com.comphenix.protocol.reflect.compiler.StructureCompiler.StructureKey; -import com.google.common.collect.Lists; -import com.google.common.util.concurrent.ThreadFactoryBuilder; - -/** - * Compiles structure modifiers on a background thread. - *

    - * This is necessary as we cannot block the main thread. - * - * @author Kristian - */ -public class BackgroundCompiler { - public static final ReportType REPORT_CANNOT_COMPILE_STRUCTURE_MODIFIER = new ReportType("Cannot compile structure. Disabing compiler."); - public static final ReportType REPORT_CANNOT_SCHEDULE_COMPILATION = new ReportType("Unable to schedule compilation task."); - - /** - * The default format for the name of new worker threads. - */ - public static final String THREAD_FORMAT = "ProtocolLib-StructureCompiler %s"; - - // How long to wait for a shutdown - public static final int SHUTDOWN_DELAY_MS = 2000; - - /** - * The default fraction of perm gen space after which the background compiler will be disabled. - */ - public static final double DEFAULT_DISABLE_AT_PERM_GEN = 0.65; - - // The single background compiler we're using - private static BackgroundCompiler backgroundCompiler; - - // Classes we're currently compiling - private Map>> listeners = new HashMap<>(); - private Object listenerLock = new Object(); - - private StructureCompiler compiler; - private boolean enabled; - private boolean shuttingDown; - - private ExecutorService executor; - private ErrorReporter reporter; - - private final Object unknownPermGenBean = new Object(); - private Object permGenBean = unknownPermGenBean; - private double disablePermGenFraction = DEFAULT_DISABLE_AT_PERM_GEN; - - /** - * Retrieves the current background compiler. - * @return Current background compiler. - */ - public static BackgroundCompiler getInstance() { - return backgroundCompiler; - } - - /** - * Sets the single background compiler we're using. - * @param backgroundCompiler - current background compiler, or NULL if the library is not loaded. - */ - public static void setInstance(BackgroundCompiler backgroundCompiler) { - BackgroundCompiler.backgroundCompiler = backgroundCompiler; - } - - /** - * Initialize a background compiler. - *

    - * Uses the default {@link #THREAD_FORMAT} to name worker threads. - * @param loader - class loader from Bukkit. - * @param reporter - current error reporter. - */ - public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter) { - ThreadFactory factory = new ThreadFactoryBuilder(). - setDaemon(true). - setNameFormat(THREAD_FORMAT). - build(); - initializeCompiler(loader, reporter, Executors.newSingleThreadExecutor(factory)); - } - - /** - * Initialize a background compiler utilizing the given thread pool. - * @param loader - class loader from Bukkit. - * @param reporter - current error reporter. - * @param executor - thread pool we'll use. - */ - public BackgroundCompiler(ClassLoader loader, ErrorReporter reporter, ExecutorService executor) { - initializeCompiler(loader, reporter, executor); - } - - // Avoid "Constructor call must be the first statement". - private void initializeCompiler(ClassLoader loader, ErrorReporter reporter, ExecutorService executor) { - if (loader == null) - throw new IllegalArgumentException("loader cannot be NULL"); - if (executor == null) - throw new IllegalArgumentException("executor cannot be NULL"); - if (reporter == null) - throw new IllegalArgumentException("reporter cannot be NULL."); - - this.compiler = new StructureCompiler(loader); - this.reporter = reporter; - this.executor = executor; - this.enabled = true; - } - - /** - * Ensure that the indirectly given structure modifier is eventually compiled. - * @param cache - store of structure modifiers. - * @param key - key of the structure modifier to compile. - */ - @SuppressWarnings("rawtypes") - public void scheduleCompilation(final Map cache, final Class key) { - - @SuppressWarnings("unchecked") - final StructureModifier uncompiled = cache.get(key); - - if (uncompiled != null) { - scheduleCompilation(uncompiled, new CompileListener() { - @Override - public void onCompiled(StructureModifier compiledModifier) { - // Update cache - cache.put(key, compiledModifier); - } - }); - } - } - - /** - * Ensure that the given structure modifier is eventually compiled. - * @param Type - * @param uncompiled - structure modifier to compile. - * @param listener - listener responsible for responding to the compilation. - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - public void scheduleCompilation(final StructureModifier uncompiled, final CompileListener listener) { - // Only schedule if we're enabled - if (enabled && !shuttingDown) { - // Check perm gen - if (getPermGenUsage() > disablePermGenFraction) - return; - - // Don't try to schedule anything - if (executor == null || executor.isShutdown()) - return; - - // Use to look up structure modifiers - final StructureKey key = new StructureKey(uncompiled); - - // Allow others to listen in too - synchronized (listenerLock) { - List list = listeners.get(key); - - if (!listeners.containsKey(key)) { - listeners.put(key, (List) Lists.newArrayList(listener)); - } else { - // We're currently compiling - list.add(listener); - return; - } - } - - // Create the worker that will compile our modifier - Callable worker = new Callable() { - @Override - public Object call() throws Exception { - StructureModifier modifier = uncompiled; - List list = null; - - // Do our compilation - try { - modifier = compiler.compile(modifier); - - synchronized (listenerLock) { - list = listeners.get(key); - - // Prevent ConcurrentModificationExceptions - if (list != null) { - list = Lists.newArrayList(list); - } - } - - // Only execute the listeners if there is a list - if (list != null) { - for (Object compileListener : list) { - ((CompileListener) compileListener).onCompiled(modifier); - } - - // Remove it when we're done - synchronized (listenerLock) { - list = listeners.remove(key); - } - } - - } catch (OutOfMemoryError e) { - setEnabled(false); - throw e; - } catch (ThreadDeath e) { - setEnabled(false); - throw e; - } catch (Throwable e) { - // Disable future compilations! - setEnabled(false); - - // Inform about this error as best as we can - reporter.reportDetailed(BackgroundCompiler.this, - Report.newBuilder(REPORT_CANNOT_COMPILE_STRUCTURE_MODIFIER).callerParam(uncompiled).error(e) - ); - } - - // We'll also return the new structure modifier - return modifier; - - } - }; - - try { - // Lookup the previous class name on the main thread. - // This is necessary as the Bukkit class loaders are not thread safe - if (compiler.lookupClassLoader(uncompiled)) { - try { - worker.call(); - } catch (Exception e) { - // Impossible! - e.printStackTrace(); - } - - } else { - - // Perform the compilation on a seperate thread - executor.submit(worker); - } - - } catch (RejectedExecutionException e) { - // Occures when the underlying queue is overflowing. Since the compilation - // is only an optmization and not really essential we'll just log this failure - // and move on. - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SCHEDULE_COMPILATION).error(e)); - } - } - } - - /** - * Add a compile listener if we are still waiting for the structure modifier to be compiled. - * @param Type - * @param uncompiled - the structure modifier that may get compiled. - * @param listener - the listener to invoke in that case. - */ - @SuppressWarnings("unchecked") - public void addListener(final StructureModifier uncompiled, final CompileListener listener) { - synchronized (listenerLock) { - StructureKey key = new StructureKey(uncompiled); - - @SuppressWarnings("rawtypes") - List list = listeners.get(key); - - if (list != null) { - list.add(listener); - } - } - } - - /** - * Retrieve the current usage of the Perm Gen space in percentage. - * @return Usage of the perm gen space. - */ - private double getPermGenUsage() { - Object permGenBean = this.permGenBean; - if (permGenBean == unknownPermGenBean) { - for (MemoryPoolMXBean item : ManagementFactory.getMemoryPoolMXBeans()) { - if (item.getName().contains("Perm Gen")) { - permGenBean = this.permGenBean = item; - break; - } - } - if (permGenBean == unknownPermGenBean) { - permGenBean = this.permGenBean = null; - } - } - if (permGenBean != null) { - MemoryUsage usage = ((MemoryPoolMXBean) permGenBean).getUsage(); - return usage.getUsed() / (double) usage.getCommitted(); - } - - // Unknown - return 0; - } - - /** - * Clean up after ourselves using the default timeout. - */ - public void shutdownAll() { - shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS); - } - - /** - * Clean up after ourselves. - * @param timeout - the maximum time to wait. - * @param unit - the time unit of the timeout argument. - */ - public void shutdownAll(long timeout, TimeUnit unit) { - setEnabled(false); - shuttingDown = true; - executor.shutdown(); - - try { - executor.awaitTermination(timeout, unit); - } catch (InterruptedException e) { - // Unlikely to ever occur - it's the main thread - e.printStackTrace(); - } - } - - /** - * Retrieve whether or not the background compiler is enabled. - * @return TRUE if it is enabled, FALSE otherwise. - */ - public boolean isEnabled() { - return enabled; - } - - /** - * Sets whether or not the background compiler is enabled. - * @param enabled - TRUE to enable it, FALSE otherwise. - */ - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - /** - * Retrieve the fraction of perm gen space used after which the background compiler will be disabled. - * @return The fraction after which the background compiler is disabled. - */ - public double getDisablePermGenFraction() { - return disablePermGenFraction; - } - - /** - * Set the fraction of perm gen space used after which the background compiler will be disabled. - * @param fraction - the maximum use of perm gen space. - */ - public void setDisablePermGenFraction(double fraction) { - this.disablePermGenFraction = fraction; - } - - /** - * Retrieve the current structure compiler. - * @return Current structure compiler. - */ - public StructureCompiler getCompiler() { - return compiler; - } -} diff --git a/src/main/java/com/comphenix/protocol/reflect/compiler/BoxingHelper.java b/src/main/java/com/comphenix/protocol/reflect/compiler/BoxingHelper.java deleted file mode 100644 index 5b52cbea..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/compiler/BoxingHelper.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.reflect.compiler; - -import net.bytebuddy.jar.asm.MethodVisitor; -import net.bytebuddy.jar.asm.Opcodes; -import net.bytebuddy.jar.asm.Type; - -/** - * Used by the compiler to automatically box and unbox values. - */ -class BoxingHelper { - - private final static Type BYTE_Type = Type.getObjectType("java/lang/Byte"); - private final static Type BOOLEAN_Type = Type.getObjectType("java/lang/Boolean"); - private final static Type SHORT_Type = Type.getObjectType("java/lang/Short"); - private final static Type CHARACTER_Type = Type.getObjectType("java/lang/Character"); - private final static Type INTEGER_Type = Type.getObjectType("java/lang/Integer"); - private final static Type FLOAT_Type = Type.getObjectType("java/lang/Float"); - private final static Type LONG_Type = Type.getObjectType("java/lang/Long"); - private final static Type DOUBLE_Type = Type.getObjectType("java/lang/Double"); - private final static Type NUMBER_Type = Type.getObjectType("java/lang/Number"); - private final static Type OBJECT_Type = Type.getObjectType("java/lang/Object"); - - private final static MethodDescriptor BOOLEAN_VALUE = MethodDescriptor.getMethod("boolean booleanValue()"); - private final static MethodDescriptor CHAR_VALUE = MethodDescriptor.getMethod("char charValue()"); - private final static MethodDescriptor INT_VALUE = MethodDescriptor.getMethod("int intValue()"); - private final static MethodDescriptor FLOAT_VALUE = MethodDescriptor.getMethod("float floatValue()"); - private final static MethodDescriptor LONG_VALUE = MethodDescriptor.getMethod("long longValue()"); - private final static MethodDescriptor DOUBLE_VALUE = MethodDescriptor.getMethod("double doubleValue()"); - - private MethodVisitor mv; - - public BoxingHelper(MethodVisitor mv) { - this.mv = mv; - } - - /** - * Generates the instructions to box the top stack value. This value is - * replaced by its boxed equivalent on top of the stack. - * - * @param type the Type of the top stack value. - */ - public void box(final Type type){ - if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { - return; - } - - if(type == Type.VOID_TYPE) { - push((String) null); - } else { - Type boxed = type; - - switch(type.getSort()) { - case Type.BYTE: - boxed = BYTE_Type; - break; - case Type.BOOLEAN: - boxed = BOOLEAN_Type; - break; - case Type.SHORT: - boxed = SHORT_Type; - break; - case Type.CHAR: - boxed = CHARACTER_Type; - break; - case Type.INT: - boxed = INTEGER_Type; - break; - case Type.FLOAT: - boxed = FLOAT_Type; - break; - case Type.LONG: - boxed = LONG_Type; - break; - case Type.DOUBLE: - boxed = DOUBLE_Type; - break; - } - - newInstance(boxed); - if(type.getSize() == 2) { - // Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o - dupX2(); - dupX2(); - pop(); - } else { - // p -> po -> opo -> oop -> o - dupX1(); - swap(); - } - - invokeConstructor(boxed, new MethodDescriptor("", Type.VOID_TYPE, new Type[] {type})); - } - } - - /** - * Generates the instruction to invoke a constructor. - * - * @param Type the class in which the constructor is defined. - * @param method the constructor to be invoked. - */ - public void invokeConstructor(final Type Type, final MethodDescriptor method){ - invokeInsn(Opcodes.INVOKESPECIAL, Type, method); - } - - /** - * Generates a DUP_X1 instruction. - */ - public void dupX1(){ - mv.visitInsn(Opcodes.DUP_X1); - } - - /** - * Generates a DUP_X2 instruction. - */ - public void dupX2(){ - mv.visitInsn(Opcodes.DUP_X2); - } - - /** - * Generates a POP instruction. - */ - public void pop(){ - mv.visitInsn(Opcodes.POP); - } - - /** - * Generates a SWAP instruction. - */ - public void swap(){ - mv.visitInsn(Opcodes.SWAP); - } - - /** - * Generates the instruction to push the given value on the stack. - * - * @param value the value to be pushed on the stack. - */ - public void push(final boolean value){ - push(value ? 1 : 0); - } - - /** - * Generates the instruction to push the given value on the stack. - * - * @param value the value to be pushed on the stack. - */ - public void push(final int value) { - if (value >= -1 && value <= 5) { - mv.visitInsn(Opcodes.ICONST_0 + value); - } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { - mv.visitIntInsn(Opcodes.BIPUSH, value); - } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { - mv.visitIntInsn(Opcodes.SIPUSH, value); - } else { - mv.visitLdcInsn(new Integer(value)); - } - } - - /** - * Generates the instruction to create a new object. - * - * @param Type the class of the object to be created. - */ - public void newInstance(final Type Type){ - TypeInsn(Opcodes.NEW, Type); - } - - /** - * Generates the instruction to push the given value on the stack. - * - * @param value the value to be pushed on the stack. May be null. - */ - public void push(final String value) { - if (value == null) { - mv.visitInsn(Opcodes.ACONST_NULL); - } else { - mv.visitLdcInsn(value); - } - } - - /** - * Generates the instructions to unbox the top stack value. This value is - * replaced by its unboxed equivalent on top of the stack. - * - * @param type - * the Type of the top stack value. - */ - public void unbox(final Type type){ - Type t = NUMBER_Type; - MethodDescriptor sig = null; - - switch(type.getSort()) { - case Type.VOID: - return; - case Type.CHAR: - t = CHARACTER_Type; - sig = CHAR_VALUE; - break; - case Type.BOOLEAN: - t = BOOLEAN_Type; - sig = BOOLEAN_VALUE; - break; - case Type.DOUBLE: - sig = DOUBLE_VALUE; - break; - case Type.FLOAT: - sig = FLOAT_VALUE; - break; - case Type.LONG: - sig = LONG_VALUE; - break; - case Type.INT: - case Type.SHORT: - case Type.BYTE: - sig = INT_VALUE; - } - - if(sig == null) { - checkCast(type); - } else { - checkCast(t); - invokeVirtual(t, sig); - } - } - - /** - * Generates the instruction to check that the top stack value is of the - * given Type. - * - * @param Type a class or interface Type. - */ - public void checkCast(final Type Type){ - if(!Type.equals(OBJECT_Type)) { - TypeInsn(Opcodes.CHECKCAST, Type); - } - } - - /** - * Generates the instruction to invoke a normal method. - * - * @param owner the class in which the method is defined. - * @param method the method to be invoked. - */ - public void invokeVirtual(final Type owner, final MethodDescriptor method){ - invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method); - } - - /** - * Generates an invoke method instruction. - * - * @param opcode the instruction's opcode. - * @param type the class in which the method is defined. - * @param method the method to be invoked. - */ - private void invokeInsn(final int opcode, final Type type, final MethodDescriptor method){ - String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName(); - mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor()); - } - - /** - * Generates a Type dependent instruction. - * - * @param opcode the instruction's opcode. - * @param type the instruction's operand. - */ - private void TypeInsn(final int opcode, final Type type){ - String desc; - - if(type.getSort() == Type.ARRAY) { - desc = type.getDescriptor(); - } else { - desc = type.getInternalName(); - } - - mv.visitTypeInsn(opcode, desc); - } -} diff --git a/src/main/java/com/comphenix/protocol/reflect/compiler/CompileListener.java b/src/main/java/com/comphenix/protocol/reflect/compiler/CompileListener.java deleted file mode 100644 index 1a91a655..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/compiler/CompileListener.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.reflect.compiler; - -import com.comphenix.protocol.reflect.StructureModifier; - -/** - * Used to save the result of an compilation. - * - * @author Kristian - * @param - type of the structure modifier field. - */ -public interface CompileListener { - /** - * Invoked when a structure modifier has been successfully compiled. - * @param compiledModifier - the compiled structure modifier. - */ - public void onCompiled(StructureModifier compiledModifier); -} diff --git a/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java b/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java deleted file mode 100644 index ae45b519..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.reflect.compiler; - -import java.lang.reflect.Field; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import com.comphenix.protocol.reflect.FieldAccessException; -import com.comphenix.protocol.reflect.StructureModifier; -import com.comphenix.protocol.reflect.instances.DefaultInstances; - -/** - * Represents a compiled structure modifier. - * - * @author Kristian - */ -public abstract class CompiledStructureModifier extends StructureModifier { - // Used to compile instances of structure modifiers - protected StructureCompiler compiler; - - // Fields that originally were read only - private Set exempted; - - public CompiledStructureModifier() { - super(); - customConvertHandling = true; - } - - @Override - public void setReadOnly(int fieldIndex, boolean value) throws FieldAccessException { - // We can remove the read-only status - if (isReadOnly(fieldIndex) && !value) { - if (exempted == null) - exempted = new HashSet<>(); - exempted.add(fieldIndex); - } - - // We can only make a certain kind of field read only - if (!isReadOnly(fieldIndex) && value) { - if (exempted == null || !exempted.contains(fieldIndex)) { - throw new IllegalStateException("Cannot make compiled field " + fieldIndex + " read only."); - } - } - } - - // Speed up the default writer - @Override - public StructureModifier writeDefaults() throws FieldAccessException { - DefaultInstances generator = DefaultInstances.DEFAULT; - - // Write a default instance to every field - for (Map.Entry entry : defaultFields.entrySet()) { - Integer index = entry.getValue(); - Field field = entry.getKey(); - - // Special case for Spigot's custom chat components - // They must be null or messages will be blank - if (field.getType().getCanonicalName().equals("net.md_5.bungee.api.chat.BaseComponent[]")) { - write(index, null); - continue; - } - - write(index, generator.getDefault(field.getType())); - } - - return this; - } - - @Override - public final Object read(int fieldIndex) throws FieldAccessException { - Object result = readGenerated(fieldIndex); - - if (converter != null) - return converter.getSpecific(result); - else - return result; - } - - /** - * Read the given field index using reflection. - * @param index - index of field. - * @return Resulting value. - * @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints. - */ - protected Object readReflected(int index) throws FieldAccessException { - return super.read(index); - } - - protected abstract Object readGenerated(int fieldIndex) throws FieldAccessException; - - @Override - public StructureModifier write(int index, Object value) throws FieldAccessException { - if (converter != null) - value = converter.getGeneric(value); - return writeGenerated(index, value); - } - - /** - * Write the given field using reflection. - * @param index - index of field. - * @param value - new value. - * @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints. - */ - protected void writeReflected(int index, Object value) throws FieldAccessException { - super.write(index, value); - } - - protected abstract StructureModifier writeGenerated(int index, Object value) throws FieldAccessException; - - @Override - public StructureModifier withTarget(Object target) { - if (compiler != null) - return compiler.compile(super.withTarget(target)); - else - return super.withTarget(target); - } -} diff --git a/src/main/java/com/comphenix/protocol/reflect/compiler/MethodDescriptor.java b/src/main/java/com/comphenix/protocol/reflect/compiler/MethodDescriptor.java deleted file mode 100644 index 7a82b22c..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/compiler/MethodDescriptor.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.reflect.compiler; - -import java.util.HashMap; -import java.util.Map; - -import net.bytebuddy.jar.asm.Type; - -/** - * Represents a method. - */ -class MethodDescriptor { - - /** - * The method name. - */ - private final String name; - - /** - * The method descriptor. - */ - private final String desc; - - /** - * Maps primitive Java type names to their descriptors. - */ - private static final Map DESCRIPTORS; - - static { - DESCRIPTORS = new HashMap(); - DESCRIPTORS.put("void", "V"); - DESCRIPTORS.put("byte", "B"); - DESCRIPTORS.put("char", "C"); - DESCRIPTORS.put("double", "D"); - DESCRIPTORS.put("float", "F"); - DESCRIPTORS.put("int", "I"); - DESCRIPTORS.put("long", "J"); - DESCRIPTORS.put("short", "S"); - DESCRIPTORS.put("boolean", "Z"); - } - - /** - * Creates a new {@link MethodDescriptor}. - * - * @param name the method's name. - * @param desc the method's descriptor. - */ - public MethodDescriptor(final String name, final String desc) { - this.name = name; - this.desc = desc; - } - - /** - * Creates a new {@link MethodDescriptor}. - * - * @param name the method's name. - * @param returnType the method's return type. - * @param argumentTypes the method's argument types. - */ - public MethodDescriptor( - final String name, - final Type returnType, - final Type[] argumentTypes) - { - this(name, Type.getMethodDescriptor(returnType, argumentTypes)); - } - - /** - * Returns a {@link MethodDescriptor} corresponding to the given Java method - * declaration. - * - * @param method a Java method declaration, without argument names, of the - * form "returnType name (argumentType1, ... argumentTypeN)", where - * the types are in plain Java (e.g. "int", "float", - * "java.util.List", ...). Classes of the java.lang package can be - * specified by their unqualified name; all other classes names must - * be fully qualified. - * @return a {@link MethodDescriptor} corresponding to the given Java method - * declaration. - * @throws IllegalArgumentException if method could not get - * parsed. - */ - public static MethodDescriptor getMethod(final String method) - throws IllegalArgumentException - { - return getMethod(method, false); - } - - /** - * Returns a {@link MethodDescriptor} corresponding to the given Java method - * declaration. - * - * @param method a Java method declaration, without argument names, of the - * form "returnType name (argumentType1, ... argumentTypeN)", where - * the types are in plain Java (e.g. "int", "float", - * "java.util.List", ...). Classes of the java.lang package may be - * specified by their unqualified name, depending on the - * defaultPackage argument; all other classes names must be fully - * qualified. - * @param defaultPackage true if unqualified class names belong to the - * default package, or false if they correspond to java.lang classes. - * For instance "Object" means "Object" if this option is true, or - * "java.lang.Object" otherwise. - * @return a {@link MethodDescriptor} corresponding to the given Java method - * declaration. - * @throws IllegalArgumentException if method could not get - * parsed. - */ - public static MethodDescriptor getMethod( - final String method, - final boolean defaultPackage) throws IllegalArgumentException - { - int space = method.indexOf(' '); - int start = method.indexOf('(', space) + 1; - int end = method.indexOf(')', start); - if (space == -1 || start == -1 || end == -1) { - throw new IllegalArgumentException(); - } - String returnType = method.substring(0, space); - String methodName = method.substring(space + 1, start - 1).trim(); - StringBuilder sb = new StringBuilder(); - sb.append('('); - int p; - do { - String s; - p = method.indexOf(',', start); - if (p == -1) { - s = map(method.substring(start, end).trim(), defaultPackage); - } else { - s = map(method.substring(start, p).trim(), defaultPackage); - start = p + 1; - } - sb.append(s); - } while (p != -1); - sb.append(')'); - sb.append(map(returnType, defaultPackage)); - return new MethodDescriptor(methodName, sb.toString()); - } - - private static String map(final String type, final boolean defaultPackage) { - if ("".equals(type)) { - return type; - } - - StringBuilder sb = new StringBuilder(); - int index = 0; - while ((index = type.indexOf("[]", index) + 1) > 0) { - sb.append('['); - } - - String t = type.substring(0, type.length() - sb.length() * 2); - String desc = DESCRIPTORS.get(t); - if (desc != null) { - sb.append(desc); - } else { - sb.append('L'); - if (t.indexOf('.') < 0) { - if (!defaultPackage) { - sb.append("java/lang/"); - } - sb.append(t); - } else { - sb.append(t.replace('.', '/')); - } - sb.append(';'); - } - return sb.toString(); - } - - /** - * Returns the name of the method described by this object. - * - * @return the name of the method described by this object. - */ - public String getName() { - return name; - } - - /** - * Returns the descriptor of the method described by this object. - * - * @return the descriptor of the method described by this object. - */ - public String getDescriptor() { - return desc; - } - - /** - * Returns the return type of the method described by this object. - * - * @return the return type of the method described by this object. - */ - public Type getReturnType() { - return Type.getReturnType(desc); - } - - /** - * Returns the argument types of the method described by this object. - * - * @return the argument types of the method described by this object. - */ - public Type[] getArgumentTypes() { - return Type.getArgumentTypes(desc); - } - - public String toString() { - return name + desc; - } - - public boolean equals(final Object o) { - if (!(o instanceof MethodDescriptor)) { - return false; - } - MethodDescriptor other = (MethodDescriptor) o; - return name.equals(other.name) && desc.equals(other.desc); - } - - public int hashCode() { - return name.hashCode() ^ desc.hashCode(); - } -} diff --git a/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java b/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java deleted file mode 100644 index 606c8dc9..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java +++ /dev/null @@ -1,571 +0,0 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.reflect.compiler; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.reflect.StructureModifier; -import com.google.common.base.Objects; -import com.google.common.primitives.Primitives; -import net.bytebuddy.jar.asm.*; - -// public class CompiledStructureModifierPacket20 extends CompiledStructureModifier { -// -// private Packet20NamedEntitySpawn typedTarget; -// -// public CompiledStructureModifierPacket20(StructureModifier other, StructureCompiler compiler) { -// super(); -// initialize(other); -// this.target = other.getTarget(); -// this.typedTarget = (Packet20NamedEntitySpawn) target; -// this.compiler = compiler; -// } -// -// @Override -// protected Object readGenerated(int fieldIndex) throws FieldAccessException { -// -// Packet20NamedEntitySpawn target = typedTarget; -// -// switch (fieldIndex) { -// case 0: return (Object) target.a; -// case 1: return (Object) target.b; -// case 2: return (Object) target.c; -// case 3: return super.readReflected(fieldIndex); -// case 4: return super.readReflected(fieldIndex); -// case 5: return (Object) target.f; -// case 6: return (Object) target.g; -// case 7: return (Object) target.h; -// default: -// throw new FieldAccessException("Invalid index " + fieldIndex); -// } -// } -// -// @Override -// protected StructureModifier writeGenerated(int index, Object value) throws FieldAccessException { -// -// Packet20NamedEntitySpawn target = typedTarget; -// -// switch (index) { -// case 0: target.a = (Integer) value; break; -// case 1: target.b = (String) value; break; -// case 2: target.c = (Integer) value; break; -// case 3: target.d = (Integer) value; break; -// case 4: super.writeReflected(index, value); break; -// case 5: super.writeReflected(index, value); break; -// case 6: target.g = (Byte) value; break; -// case 7: target.h = (Integer) value; break; -// default: -// throw new FieldAccessException("Invalid index " + index); -// } -// -// // Chaining -// return this; -// } -// } - -/** - * Represents a StructureModifier compiler. - * - * @author Kristian - */ -public final class StructureCompiler { - public static final ReportType REPORT_TOO_MANY_GENERATED_CLASSES = new ReportType("Generated too many classes (count: %s)"); - - // Used to store generated classes of different types - @SuppressWarnings("rawtypes") - static class StructureKey { - private Class targetType; - private Class fieldType; - - public StructureKey(StructureModifier source) { - this(source.getTargetType(), source.getFieldType()); - } - - public StructureKey(Class targetType, Class fieldType) { - this.targetType = targetType; - this.fieldType = fieldType; - } - - @Override - public int hashCode() { - return Objects.hashCode(targetType, fieldType); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof StructureKey) { - StructureKey other = (StructureKey) obj; - return Objects.equal(targetType, other.targetType) && - Objects.equal(fieldType, other.fieldType); - } - return false; - } - } - - // Used to load classes - private volatile static Method defineMethod; - - @SuppressWarnings("rawtypes") - private Map compiledCache = new ConcurrentHashMap(); - - // The class loader we'll store our classes - private ClassLoader loader; - - // References to other classes - private static String PACKAGE_NAME = "com/comphenix/protocol/reflect/compiler"; - private static String SUPER_CLASS = "com/comphenix/protocol/reflect/StructureModifier"; - private static String COMPILED_CLASS = PACKAGE_NAME + "/CompiledStructureModifier"; - private static String FIELD_EXCEPTION_CLASS = "com/comphenix/protocol/reflect/FieldAccessException"; - - // On java 9+ (53.0+) CLassLoader#defineClass(String, byte[], int, int) should not be used anymore. - // It will throw warnings and on Java 16+ (60.0+), it does not work at all anymore. - private static final boolean LEGACY_CLASS_DEFINITION = - Float.parseFloat(System.getProperty("java.class.version")) < 53; - /** - * The MethodHandles.Lookup object for this compiler. Only used when using the modern defineClass strategy. - */ - private Object lookup = null; - - // Used to get the MethodHandles.Lookup object on newer versions of Java. - private volatile static Method lookupMethod; - - public static boolean attemptClassLoad = false; - - /** - * Construct a structure compiler. - * @param loader - main class loader. - */ - StructureCompiler(ClassLoader loader) { - this.loader = loader; - } - - /** - * Lookup the current class loader for any previously generated classes before we attempt to generate something. - * @param Type - * @param source - the structure modifier to look up. - * @return TRUE if we successfully found a previously generated class, FALSE otherwise. - */ - public boolean lookupClassLoader(StructureModifier source) { - StructureKey key = new StructureKey(source); - - // See if there's a need to lookup the class name - if (compiledCache.containsKey(key)) { - return true; - } - - if (! attemptClassLoad) { - return false; - } - - // This causes a ton of lag and doesn't seem to work - - try { - String className = getCompiledName(source); - - // This class might have been generated before. Try to load it. - Class before = loader.loadClass(PACKAGE_NAME.replace('/', '.') + "." + className); - - if (before != null) { - compiledCache.put(key, before); - return true; - } - } catch (ClassNotFoundException e) { - // That's ok. - } - - // We need to compile the class - return false; - } - - /** - * Compiles the given structure modifier. - *

    - * WARNING: Do NOT call this method in the main thread. Compiling may easily take 10 ms, which is already - * over 1/4 of a tick (50 ms). Let the background thread automatically compile the structure modifiers instead. - * @param Type - * @param source - structure modifier to compile. - * @return A compiled structure modifier. - */ - @SuppressWarnings("unchecked") - public synchronized StructureModifier compile(StructureModifier source) { - - // We cannot optimize a structure modifier with no public fields - if (!isAnyPublic(source.getFields())) { - return source; - } - - StructureKey key = new StructureKey(source); - Class compiledClass = compiledCache.get(key); - - if (!compiledCache.containsKey(key)) { - compiledClass = generateClass(source); - compiledCache.put(key, compiledClass); - } - - // Next, create an instance of this class - try { - return (StructureModifier) compiledClass.getConstructor( - StructureModifier.class, StructureCompiler.class). - newInstance(source, this); - } catch (OutOfMemoryError e) { - // Print the number of generated classes by the current instances - ProtocolLibrary.getErrorReporter().reportWarning( - this, Report.newBuilder(REPORT_TOO_MANY_GENERATED_CLASSES).messageParam(compiledCache.size()) - ); - throw e; - } catch (IllegalArgumentException e) { - throw new IllegalStateException("Used invalid parameters in instance creation", e); - } catch (SecurityException e) { - throw new RuntimeException("Security limitation!", e); - } catch (InstantiationException e) { - throw new RuntimeException("Error occured while instancing generated class.", e); - } catch (IllegalAccessException e) { - throw new RuntimeException("Security limitation! Cannot create instance of dynamic class.", e); - } catch (InvocationTargetException e) { - throw new RuntimeException("Error occured while instancing generated class.", e); - } catch (NoSuchMethodException e) { - throw new IllegalStateException("Cannot happen.", e); - } - } - - /** - * Retrieve a variable identifier that can uniquely represent the given type. - * @param type - a type. - * @return A unique and legal identifier for the given type. - */ - private String getSafeTypeName(Class type) { - return type.getCanonicalName().replace("[]", "Array").replace(".", "_"); - } - - /** - * Retrieve the compiled name of a given structure modifier. - * @param source - the structure modifier. - * @return The unique, compiled name of a compiled structure modifier. - */ - private String getCompiledName(StructureModifier source) { - Class targetType = source.getTargetType(); - - // Concat class and field type - return "CompiledStructure$" + - getSafeTypeName(targetType) + "$" + - getSafeTypeName(source.getFieldType()); - } - - /** - * Compile a structure modifier. - * @param source - structure modifier. - * @return The compiled structure modifier. - */ - private Class generateClass(StructureModifier source) { - - ClassWriter cw = new ClassWriter(0); - Class targetType = source.getTargetType(); - - String className = getCompiledName(source); - String targetSignature = Type.getDescriptor(targetType); - String targetName = targetType.getName().replace('.', '/'); - - // Define class - cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className, - null, COMPILED_CLASS, null); - - createFields(cw, targetSignature); - createConstructor(cw, className, targetSignature, targetName); - createReadMethod(cw, className, source.getFields(), targetSignature, targetName); - createWriteMethod(cw, className, source.getFields(), targetSignature, targetName); - cw.visitEnd(); - - byte[] data = cw.toByteArray(); - - Class clazz = defineClass(data); - // DEBUG CODE: Print the content of the generated class. - //org.objectweb.asm.ClassReader cr = new org.objectweb.asm.ClassReader(data); - //cr.accept(new ASMifierClassVisitor(new PrintWriter(System.out)), 0); - return clazz; - } - - private Class defineClassLegacy(byte[] data) throws InvocationTargetException, IllegalAccessException, - NoSuchMethodException { - if (defineMethod == null) { - Method defined = ClassLoader.class.getDeclaredMethod("defineClass", - new Class[]{String.class, byte[].class, int.class, int.class}); - - // Awesome. Now, create and return it. - defined.setAccessible(true); - defineMethod = defined; - } - return (Class) defineMethod.invoke(loader, null, data, 0, data.length); - } - - private Class defineClassModern(byte[] data) throws InvocationTargetException, IllegalAccessException, - ClassNotFoundException, NoSuchMethodException { - if (defineMethod == null) { - defineMethod = Class.forName("java.lang.invoke.MethodHandles$Lookup") - .getDeclaredMethod("defineClass", byte[].class); - } - if (lookupMethod == null) { - lookupMethod = Class.forName("java.lang.invoke.MethodHandles").getDeclaredMethod("lookup"); - } - if (lookup == null) - lookup = lookupMethod.invoke(null); - - return (Class) defineMethod.invoke(lookup, data); - } - - private Class defineClass(byte[] data) { - try { - return LEGACY_CLASS_DEFINITION ? defineClassLegacy(data) : defineClassModern(data); - } catch (SecurityException e) { - throw new RuntimeException("Cannot use reflection to dynamically load a class.", e); - } catch (NoSuchMethodException | ClassNotFoundException e) { - throw new IllegalStateException("Incompatible JVM.", e); - } catch (IllegalArgumentException e) { - throw new IllegalStateException("Cannot call defineMethod - wrong JVM?", e); - } catch (IllegalAccessException e) { - throw new RuntimeException("Security limitation! Cannot dynamically load class.", e); - } catch (InvocationTargetException e) { - throw new RuntimeException("Error occurred in code generator.", e); - } - } - - /** - * Determine if at least one of the given fields is public. - * @param fields - field to test. - * @return TRUE if one or more field is publically accessible, FALSE otherwise. - */ - private boolean isAnyPublic(List fields) { - // Are any of the fields public? - for (Field field : fields) { - if (isPublic(field)) { - return true; - } - } - return false; - } - - private boolean isPublic(Field field) { - return Modifier.isPublic(field.getModifiers()); - } - - private boolean isNonFinal(Field field) { - return !Modifier.isFinal(field.getModifiers()); - } - - private void createFields(ClassWriter cw, String targetSignature) { - FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null); - typedField.visitEnd(); - } - - private void createWriteMethod(ClassWriter cw, String className, List fields, String targetSignature, String targetName) { - - String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";"; - String methodSignature = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";"; - MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature, - new String[] { FIELD_EXCEPTION_CLASS }); - BoxingHelper boxingHelper = new BoxingHelper(mv); - - String generatedClassName = PACKAGE_NAME + "/" + className; - - mv.visitCode(); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitFieldInsn(Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature); - mv.visitVarInsn(Opcodes.ASTORE, 3); - mv.visitVarInsn(Opcodes.ILOAD, 1); - - // The last $Label is for the default switch - Label[] $Labels = new Label[fields.size()]; - Label error$Label = new Label(); - Label return$Label = new Label(); - - // Generate $Labels - for (int i = 0; i < fields.size(); i++) { - $Labels[i] = new Label(); - } - - mv.visitTableSwitchInsn(0, $Labels.length - 1, error$Label, $Labels); - - for (int i = 0; i < fields.size(); i++) { - - Field field = fields.get(i); - Class outputType = field.getType(); - Class inputType = Primitives.wrap(outputType); - String typeDescriptor = Type.getDescriptor(outputType); - String inputPath = inputType.getName().replace('.', '/'); - - mv.visitLabel($Labels[i]); - - // Push the compare object - if (i == 0) - mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null); - else - mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - - // Only write to public non-final fields - if (isPublic(field) && isNonFinal(field)) { - mv.visitVarInsn(Opcodes.ALOAD, 3); - mv.visitVarInsn(Opcodes.ALOAD, 2); - - if (!outputType.isPrimitive()) - mv.visitTypeInsn(Opcodes.CHECKCAST, inputPath); - else - boxingHelper.unbox(Type.getType(outputType)); - - mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, field.getName(), typeDescriptor); - - } else { - // Use reflection. We don't have a choice, unfortunately. - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitVarInsn(Opcodes.ILOAD, 1); - mv.visitVarInsn(Opcodes.ALOAD, 2); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedClassName, "writeReflected", "(ILjava/lang/Object;)V"); - } - - mv.visitJumpInsn(Opcodes.GOTO, return$Label); - } - - mv.visitLabel(error$Label); - mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS); - mv.visitInsn(Opcodes.DUP); - mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); - mv.visitInsn(Opcodes.DUP); - mv.visitLdcInsn("Invalid index "); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V"); - mv.visitVarInsn(Opcodes.ILOAD, 1); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "", "(Ljava/lang/String;)V"); - mv.visitInsn(Opcodes.ATHROW); - - mv.visitLabel(return$Label); - mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitInsn(Opcodes.ARETURN); - mv.visitMaxs(5, 4); - mv.visitEnd(); - } - - private void createReadMethod(ClassWriter cw, String className, List fields, String targetSignature, String targetName) { - MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null, - new String[] { "com/comphenix/protocol/reflect/FieldAccessException" }); - BoxingHelper boxingHelper = new BoxingHelper(mv); - - String generatedClassName = PACKAGE_NAME + "/" + className; - - mv.visitCode(); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitFieldInsn(Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature); - mv.visitVarInsn(Opcodes.ASTORE, 2); - mv.visitVarInsn(Opcodes.ILOAD, 1); - - // The last $Label is for the default switch - Label[] $Labels = new Label[fields.size()]; - Label error$Label = new Label(); - - // Generate $Labels - for (int i = 0; i < fields.size(); i++) { - $Labels[i] = new Label(); - } - - mv.visitTableSwitchInsn(0, fields.size() - 1, error$Label, $Labels); - - for (int i = 0; i < fields.size(); i++) { - - Field field = fields.get(i); - Class outputType = field.getType(); - String typeDescriptor = Type.getDescriptor(outputType); - - mv.visitLabel($Labels[i]); - - // Push the compare object - if (i == 0) - mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null); - else - mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - - // Note that byte code cannot access non-public fields - if (isPublic(field)) { - mv.visitVarInsn(Opcodes.ALOAD, 2); - mv.visitFieldInsn(Opcodes.GETFIELD, targetName, field.getName(), typeDescriptor); - - boxingHelper.box(Type.getType(outputType)); - } else { - // We have to use reflection for private and protected fields. - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitVarInsn(Opcodes.ILOAD, 1); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedClassName, "readReflected", "(I)Ljava/lang/Object;"); - } - - mv.visitInsn(Opcodes.ARETURN); - } - - mv.visitLabel(error$Label); - mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS); - mv.visitInsn(Opcodes.DUP); - mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); - mv.visitInsn(Opcodes.DUP); - mv.visitLdcInsn("Invalid index "); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V"); - mv.visitVarInsn(Opcodes.ILOAD, 1); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "", "(Ljava/lang/String;)V"); - mv.visitInsn(Opcodes.ATHROW); - mv.visitMaxs(5, 3); - mv.visitEnd(); - } - - private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) { - MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "", - "(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V", - "(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V", null); - String fullClassName = PACKAGE_NAME + "/" + className; - - mv.visitCode(); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "", "()V"); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitVarInsn(Opcodes.ALOAD, 1); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, fullClassName, "initialize", "(L" + SUPER_CLASS + ";)V"); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitVarInsn(Opcodes.ALOAD, 1); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;"); - mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "target", "Ljava/lang/Object;"); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitFieldInsn(Opcodes.GETFIELD, fullClassName, "target", "Ljava/lang/Object;"); - mv.visitTypeInsn(Opcodes.CHECKCAST, targetName); - mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "typedTarget", targetSignature); - mv.visitVarInsn(Opcodes.ALOAD, 0); - mv.visitVarInsn(Opcodes.ALOAD, 2); - mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;"); - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(2, 3); - mv.visitEnd(); - } -} diff --git a/src/main/java/com/comphenix/protocol/reflect/fuzzy/AbstractFuzzyMatcher.java b/src/main/java/com/comphenix/protocol/reflect/fuzzy/AbstractFuzzyMatcher.java index 27108835..ba6aaf2f 100644 --- a/src/main/java/com/comphenix/protocol/reflect/fuzzy/AbstractFuzzyMatcher.java +++ b/src/main/java/com/comphenix/protocol/reflect/fuzzy/AbstractFuzzyMatcher.java @@ -1,149 +1,52 @@ package com.comphenix.protocol.reflect.fuzzy; -import com.google.common.primitives.Ints; - /** * Represents a matcher for fields, methods, constructors and classes. *

    * This class should ideally never expose mutable state. Its round number must be immutable. + * * @author Kristian */ -public abstract class AbstractFuzzyMatcher implements Comparable> { - private Integer roundNumber; - +@FunctionalInterface +public interface AbstractFuzzyMatcher { + /** * Determine if the given value is a match. - * @param value - the value to match. + * + * @param value - the value to match. * @param parent - the parent container, or NULL if this value is the root. * @return TRUE if it is a match, FALSE otherwise. */ - public abstract boolean isMatch(T value, Object parent); - - /** - * Calculate the round number indicating when this matcher should be applied. - *

    - * Matchers with a lower round number are applied before matchers with a higher round number. - *

    - * By convention, this round number should be negative, except for zero in the case of a matcher - * that accepts any value. A good implementation should return the inverted tree depth (class hierachy) - * of the least specified type used in the matching. Thus {@link Integer} will have a lower round number than - * {@link Number}. - * - * @return A number (positive or negative) that is used to order matchers. - */ - protected abstract int calculateRoundNumber(); - - /** - * Retrieve the cached round number. This should never change once calculated. - *

    - * Matchers with a lower round number are applied before matchers with a higher round number. - * @return The round number. - * @see #calculateRoundNumber() - */ - public final int getRoundNumber() { - if (roundNumber == null) { - return roundNumber = calculateRoundNumber(); - } else { - return roundNumber; - } - } - - /** - * Combine two round numbers by taking the highest non-zero number, or return zero. - * @param roundA - the first round number. - * @param roundB - the second round number. - * @return The combined round number. - */ - protected final int combineRounds(int roundA, int roundB) { - if (roundA == 0) - return roundB; - else if (roundB == 0) - return roundA; - else - return Math.max(roundA, roundB); - } - - /** - * Combine n round numbers by taking the highest non-zero number, or return zero. - * @param rounds - the round numbers. - * @return The combined round number. - */ - protected final int combineRounds(Integer... rounds) { - if (rounds.length < 2) - throw new IllegalArgumentException("Must supply at least two arguments."); - - // Get the seed - int reduced = combineRounds(rounds[0], rounds[1]); - - // Aggregate it all - for (int i = 2; i < rounds.length; i++) { - reduced = combineRounds(reduced, rounds[i]); - } - return reduced; - } - - @Override - public int compareTo(AbstractFuzzyMatcher obj) { - return Ints.compare(getRoundNumber(), obj.getRoundNumber()); - } + boolean isMatch(T value, Object parent); /** * Create a fuzzy matcher that returns the opposite result of the current matcher. + * * @return An inverted fuzzy matcher. */ - public AbstractFuzzyMatcher inverted() { - return new AbstractFuzzyMatcher() { - @Override - public boolean isMatch(T value, Object parent) { - return !AbstractFuzzyMatcher.this.isMatch(value, parent); - } - - @Override - protected int calculateRoundNumber() { - return -2; - } - }; + default AbstractFuzzyMatcher inverted() { + return (value, parent) -> !this.isMatch(value, parent); } - + /** * Require that this and the given matcher be TRUE. + * * @param other - the other fuzzy matcher. * @return A combined fuzzy matcher. */ - public AbstractFuzzyMatcher and(final AbstractFuzzyMatcher other) { - return new AbstractFuzzyMatcher() { - @Override - public boolean isMatch(T value, Object parent) { - // They both have to be true - return AbstractFuzzyMatcher.this.isMatch(value, parent) && - other.isMatch(value, parent); - } - - @Override - protected int calculateRoundNumber() { - return combineRounds(AbstractFuzzyMatcher.this.getRoundNumber(), other.getRoundNumber()); - } - }; + default AbstractFuzzyMatcher and(final AbstractFuzzyMatcher other) { + // They both have to be true + return (value, parent) -> this.isMatch(value, parent) && other.isMatch(value, parent); } - + /** * Require that either this or the other given matcher be TRUE. + * * @param other - the other fuzzy matcher. * @return A combined fuzzy matcher. */ - public AbstractFuzzyMatcher or(final AbstractFuzzyMatcher other) { - return new AbstractFuzzyMatcher() { - @Override - public boolean isMatch(T value, Object parent) { - // Either can be true - return AbstractFuzzyMatcher.this.isMatch(value, parent) || - other.isMatch(value, parent); - } - - @Override - protected int calculateRoundNumber() { - return combineRounds(AbstractFuzzyMatcher.this.getRoundNumber(), other.getRoundNumber()); - } - }; + default AbstractFuzzyMatcher or(final AbstractFuzzyMatcher other) { + // Either can be true + return (value, parent) -> this.isMatch(value, parent) || other.isMatch(value, parent); } } diff --git a/src/main/java/com/comphenix/protocol/reflect/fuzzy/AbstractFuzzyMember.java b/src/main/java/com/comphenix/protocol/reflect/fuzzy/AbstractFuzzyMember.java index d320b47c..fb30ca14 100644 --- a/src/main/java/com/comphenix/protocol/reflect/fuzzy/AbstractFuzzyMember.java +++ b/src/main/java/com/comphenix/protocol/reflect/fuzzy/AbstractFuzzyMember.java @@ -1,170 +1,37 @@ package com.comphenix.protocol.reflect.fuzzy; +import com.google.common.collect.Maps; import java.lang.reflect.Member; import java.lang.reflect.Modifier; -import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import java.util.regex.Pattern; - import javax.annotation.Nonnull; -import com.google.common.base.Objects; - /** * Represents a matcher that matches members. - * - * @author Kristian + * * @param - type that it matches. + * @author Kristian */ -public abstract class AbstractFuzzyMember extends AbstractFuzzyMatcher { +public abstract class AbstractFuzzyMember implements AbstractFuzzyMatcher { + // Accessibility matchers protected int modifiersRequired; protected int modifiersBanned; - + protected Pattern nameRegex; - protected AbstractFuzzyMatcher> declaringMatcher = ClassExactMatcher.MATCH_ALL; - + protected AbstractFuzzyMatcher> declaringMatcher = ClassTypeMatcher.MATCH_ALL; + /** - * Whether or not this contract can be modified. + * Whether this contract can be modified. */ protected transient boolean sealed; - - /** - * Represents a builder of a fuzzy member contract. - * - * @author Kristian - */ - public static abstract class Builder> { - protected T member = initialMember(); - /** - * Add a given bit-field of required modifiers for every matching member. - * @param modifier - bit-field of modifiers that are required. - * @return This builder, for chaining. - */ - public Builder requireModifier(int modifier) { - member.modifiersRequired |= modifier; - return this; - } - - /** - * Require that every matching member is public. - * @return This builder, for chaining. - */ - public Builder requirePublic() { - return requireModifier(Modifier.PUBLIC); - } - - /** - * Add a given bit-field of modifers that will skip or ignore members. - * @param modifier - bit-field of modifiers to skip or ignore. - * @return This builder, for chaining. - */ - public Builder banModifier(int modifier) { - member.modifiersBanned |= modifier; - return this; - } - - /** - * Set the regular expresson that matches a members name. - * @param regex - new regular expression of valid names. - * @return This builder, for chaining. - */ - public Builder nameRegex(String regex) { - member.nameRegex = Pattern.compile(regex); - return this; - } - - /** - * Set the regular expression pattern that matches a members name. - * @param pattern - regular expression pattern for a valid name. - * @return This builder, for chaining. - */ - public Builder nameRegex(Pattern pattern) { - member.nameRegex = pattern; - return this; - } - - /** - * Set the exact name of the member we are matching. - *

    - * This will overwrite the regular expression rule. - * @param name - exact name. - * @return This builder, for chaining. - */ - public Builder nameExact(String name) { - return nameRegex(Pattern.quote(name)); - } - - /** - * Require that a member is defined by this exact class. - * @param declaringClass - the declaring class of any matching member. - * @return This builder, for chaining. - */ - public Builder declaringClassExactType(Class declaringClass) { - member.declaringMatcher = FuzzyMatchers.matchExact(declaringClass); - return this; - } - - /** - * Require that a member is defined by this exact class, or any super class. - * @param declaringClass - the declaring class. - * @return This builder, for chaining. - */ - public Builder declaringClassSuperOf(Class declaringClass) { - member.declaringMatcher = FuzzyMatchers.matchSuper(declaringClass); - return this; - } - - /** - * Require that a member is defined by this exact class, or any super class. - * @param declaringClass - the declaring class. - * @return This builder, for chaining. - */ - public Builder declaringClassDerivedOf(Class declaringClass) { - member.declaringMatcher = FuzzyMatchers.matchDerived(declaringClass); - return this; - } - - /** - * Require that a member is defined by a class that matches the given matcher. - * @param classMatcher - class matcher. - * @return This builder, for chaining. - */ - public Builder declaringClassMatching(AbstractFuzzyMatcher> classMatcher) { - member.declaringMatcher = classMatcher; - return this; - } - - /** - * Construct a new instance of the current type. - * @return New instance. - */ - @Nonnull - protected abstract T initialMember(); - - /** - * Build a new instance of this type. - *

    - * Builders should call {@link AbstractFuzzyMember#prepareBuild()} when constructing new objects. - * @return New instance of this type. - */ - public abstract T build(); - } - protected AbstractFuzzyMember() { // Only allow construction through the builder } - - /** - * Called before a builder is building a member and copying its state. - *

    - * Use this to prepare any special values. - */ - protected void prepareBuild() { - // No need to prepare anything - } - + // Clone a given contract protected AbstractFuzzyMember(AbstractFuzzyMember other) { this.modifiersRequired = other.modifiersRequired; @@ -174,131 +41,279 @@ public abstract class AbstractFuzzyMember extends AbstractFuzz this.sealed = true; } - /** - * Retrieve a bit field of every {@link java.lang.reflect.Modifier Modifier} that is required for the member to match. - * @return A required modifier bit field. - */ - public int getModifiersRequired() { - return modifiersRequired; + private static String getBitView(int value, int bits) { + if (bits < 0 || bits > 31) { + throw new IllegalArgumentException("Bits must be a value between 0 and 32"); + } + + // Extract our needed bits + int snipped = value & ((1 << bits) - 1); + return Integer.toBinaryString(snipped); } /** - * Retrieve a bit field of every {@link java.lang.reflect.Modifier Modifier} that must not be present for the member to match. + * Called before a builder is building a member and copying its state. + *

    + * Use this to prepare any special values. + */ + protected void prepareBuild() { + // No need to prepare anything + } + + /** + * Retrieve a bit field of every {@link java.lang.reflect.Modifier Modifier} that is required for the member to + * match. + * + * @return A required modifier bit field. + */ + public int getModifiersRequired() { + return this.modifiersRequired; + } + + /** + * Retrieve a bit field of every {@link java.lang.reflect.Modifier Modifier} that must not be present for the member + * to match. + * * @return A banned modifier bit field. */ public int getModifiersBanned() { - return modifiersBanned; + return this.modifiersBanned; } /** * Retrieve the regular expression pattern that is used to match the name of a member. + * * @return The regex matching a name, or NULL if everything matches. */ public Pattern getNameRegex() { - return nameRegex; + return this.nameRegex; } /** * Retrieve a class matcher for the declaring class of the member. + * * @return An object matching the declaring class. */ public AbstractFuzzyMatcher> getDeclaringMatcher() { - return declaringMatcher; + return this.declaringMatcher; } @Override public boolean isMatch(T value, Object parent) { int mods = value.getModifiers(); - + // Match accessibility and name - return (mods & modifiersRequired) == modifiersRequired && - (mods & modifiersBanned) == 0 && - declaringMatcher.isMatch(value.getDeclaringClass(), value) && - isNameMatch(value.getName()); + return (mods & this.modifiersRequired) == this.modifiersRequired + && (mods & this.modifiersBanned) == 0 + && this.declaringMatcher.isMatch(value.getDeclaringClass(), value) + && this.isNameMatch(value.getName()); } - + /** * Determine if a given name matches the current member matcher. + * * @param name - the name to match. * @return TRUE if the name matches, FALSE otherwise. */ private boolean isNameMatch(String name) { - if (nameRegex == null) + if (this.nameRegex == null) { return true; - else - return nameRegex.matcher(name).matches(); + } else { + return this.nameRegex.matcher(name).matches(); + } } - @Override - protected int calculateRoundNumber() { - // Sanity check - if (!sealed) - throw new IllegalStateException("Cannot calculate round number during construction."); - - // NULL is zero - return declaringMatcher.getRoundNumber(); - } - @Override public String toString() { - return getKeyValueView().toString(); + return this.getKeyValueView().toString(); } - + /** * Generate a view of this matcher as a key-value map. *

    * Used by {@link #toString()} to print a representation of this object. + * * @return A modifiable key-value view. */ protected Map getKeyValueView() { - final Map map = new LinkedHashMap<>(); - + Map map = Maps.newLinkedHashMap(); + // Build our representation - if (modifiersRequired != Integer.MAX_VALUE || modifiersBanned != 0) { - map.put("modifiers", String.format("[required: %s, banned: %s]", - getBitView(modifiersRequired, 16), - getBitView(modifiersBanned, 16)) - ); + if (this.modifiersRequired != Integer.MAX_VALUE || this.modifiersBanned != 0) { + map.put("modifiers", String.format( + "[required: %s, banned: %s]", + getBitView(this.modifiersRequired, 16), + getBitView(this.modifiersBanned, 16))); } - if (nameRegex != null) { - map.put("name", nameRegex.pattern()); + + if (this.nameRegex != null) { + map.put("name", this.nameRegex.pattern()); } - if (declaringMatcher != ClassExactMatcher.MATCH_ALL) { - map.put("declaring", declaringMatcher); + + if (this.declaringMatcher != ClassTypeMatcher.MATCH_ALL) { + map.put("declaring", this.declaringMatcher); } - + return map; } - - private static String getBitView(int value, int bits) { - if (bits < 0 || bits > 31) - throw new IllegalArgumentException("Bits must be a value between 0 and 32"); - - // Extract our needed bits - int snipped = value & ((1 << bits) - 1); - return Integer.toBinaryString(snipped); - } - + @Override public boolean equals(Object obj) { - // Immutablity is awesome if (this == obj) { return true; } else if (obj instanceof AbstractFuzzyMember) { - @SuppressWarnings("unchecked") - AbstractFuzzyMember other = (AbstractFuzzyMember) obj; - - return modifiersBanned == other.modifiersBanned && - modifiersRequired == other.modifiersRequired && - FuzzyMatchers.checkPattern(nameRegex, other.nameRegex) && - Objects.equal(declaringMatcher, other.declaringMatcher); + AbstractFuzzyMember other = (AbstractFuzzyMember) obj; + return this.modifiersBanned == other.modifiersBanned + && this.modifiersRequired == other.modifiersRequired + && FuzzyMatchers.checkPattern(this.nameRegex, other.nameRegex) + && Objects.equals(this.declaringMatcher, other.declaringMatcher); + } else { + return false; } - return false; } - + @Override public int hashCode() { - return Objects.hashCode(modifiersBanned, modifiersRequired, - nameRegex != null ? nameRegex.pattern() : null, declaringMatcher); + return Objects.hash( + this.modifiersBanned, + this.modifiersRequired, + this.nameRegex != null ? this.nameRegex.pattern() : null, + this.declaringMatcher); + } + + /** + * Represents a builder of a fuzzy member contract. + * + * @author Kristian + */ + public static abstract class Builder> { + + protected T member = this.initialMember(); + + /** + * Add a given bit-field of required modifiers for every matching member. + * + * @param modifier - bit-field of modifiers that are required. + * @return This builder, for chaining. + */ + public Builder requireModifier(int modifier) { + this.member.modifiersRequired |= modifier; + return this; + } + + /** + * Require that every matching member is public. + * + * @return This builder, for chaining. + */ + public Builder requirePublic() { + return this.requireModifier(Modifier.PUBLIC); + } + + /** + * Add a given bit-field of modifers that will skip or ignore members. + * + * @param modifier - bit-field of modifiers to skip or ignore. + * @return This builder, for chaining. + */ + public Builder banModifier(int modifier) { + this.member.modifiersBanned |= modifier; + return this; + } + + /** + * Set the regular expresson that matches a members name. + * + * @param regex - new regular expression of valid names. + * @return This builder, for chaining. + */ + public Builder nameRegex(String regex) { + this.member.nameRegex = Pattern.compile(regex); + return this; + } + + /** + * Set the regular expression pattern that matches a members name. + * + * @param pattern - regular expression pattern for a valid name. + * @return This builder, for chaining. + */ + public Builder nameRegex(Pattern pattern) { + this.member.nameRegex = pattern; + return this; + } + + /** + * Set the exact name of the member we are matching. + *

    + * This will overwrite the regular expression rule. + * + * @param name - exact name. + * @return This builder, for chaining. + */ + public Builder nameExact(String name) { + return this.nameRegex(Pattern.quote(name)); + } + + /** + * Require that a member is defined by this exact class. + * + * @param declaringClass - the declaring class of any matching member. + * @return This builder, for chaining. + */ + public Builder declaringClassExactType(Class declaringClass) { + this.member.declaringMatcher = FuzzyMatchers.matchExact(declaringClass); + return this; + } + + /** + * Require that a member is defined by this exact class, or any super class. + * + * @param declaringClass - the declaring class. + * @return This builder, for chaining. + */ + public Builder declaringClassSuperOf(Class declaringClass) { + this.member.declaringMatcher = FuzzyMatchers.matchSuper(declaringClass); + return this; + } + + /** + * Require that a member is defined by this exact class, or any super class. + * + * @param declaringClass - the declaring class. + * @return This builder, for chaining. + */ + public Builder declaringClassDerivedOf(Class declaringClass) { + this.member.declaringMatcher = FuzzyMatchers.matchDerived(declaringClass); + return this; + } + + /** + * Require that a member is defined by a class that matches the given matcher. + * + * @param classMatcher - class matcher. + * @return This builder, for chaining. + */ + public Builder declaringClassMatching(AbstractFuzzyMatcher> classMatcher) { + this.member.declaringMatcher = classMatcher; + return this; + } + + /** + * Construct a new instance of the current type. + * + * @return New instance. + */ + @Nonnull + protected abstract T initialMember(); + + /** + * Build a new instance of this type. + *

    + * Builders should call {@link AbstractFuzzyMember#prepareBuild()} when constructing new objects. + * + * @return New instance of this type. + */ + public abstract T build(); } } diff --git a/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassExactMatcher.java b/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassExactMatcher.java deleted file mode 100644 index 6d52ace6..00000000 --- a/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassExactMatcher.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.comphenix.protocol.reflect.fuzzy; - -import com.google.common.base.Objects; - -/** - * Used to check class equality. - * - * @author Kristian - */ -class ClassExactMatcher extends AbstractFuzzyMatcher> { - /** - * Different matching rules. - */ - enum Options { - /** - * Match classes exactly. - */ - MATCH_EXACT, - - /** - * A match if the input class is a superclass of the matcher class, or the same class. - */ - MATCH_SUPER, - - /** - * A match if the input class is a derived class of the matcher class, or the same class. - */ - MATCH_DERIVED - } - - /** - * Match any class. - */ - public static final ClassExactMatcher MATCH_ALL = new ClassExactMatcher(null, Options.MATCH_SUPER); - - private final Class matcher; - private final Options option; - - /** - * Constructs a new class matcher. - * @param matcher - the matching class, or NULL to represent anything. - * @param option - options specifying the matching rules. - */ - ClassExactMatcher(Class matcher, Options option) { - this.matcher = matcher; - this.option = option; - } - - /** - * Determine if a given class is equivalent. - *

    - * If the matcher is NULL, the result will only be TRUE if we're not matching exactly. - * @param input - the input class defined in the source file. - * @param parent - the container that holds a reference to this class. - * @return TRUE if input matches according to the rules in {@link #getOptions()}, FALSE otherwise. - */ - @Override - public boolean isMatch(Class input, Object parent) { - if (input == null) throw new IllegalArgumentException("Input class cannot be NULL."); - - // Do our checking - if (matcher == null) return option != Options.MATCH_EXACT; - - switch (option) { - case MATCH_SUPER: - return input.isAssignableFrom(matcher); // matcher instanceof input - case MATCH_DERIVED: - return matcher.isAssignableFrom(input); // input instanceof matcher - case MATCH_EXACT: - return input.equals(matcher); - default: - throw new IllegalStateException("Unknown option."); - } - } - - @Override - protected int calculateRoundNumber() { - return -getClassNumber(matcher); - } - - /** - * Retrieve the number of superclasses of the specific class. - *

    - * Object is represented as one. All interfaces are one, unless they're derived. - * @param clazz - the class to test. - * @return The number of superclasses. - */ - public static int getClassNumber(Class clazz) { - int count = 0; - - // Move up the hierachy - while (clazz != null) { - ++count; - clazz = clazz.getSuperclass(); - } - return count; - } - - /** - * Retrieve the class we're comparing against. - * @return Class to compare against. - */ - public Class getMatcher() { - return matcher; - } - - /** - * The matching rules for this class matcher. - * @return The current matching option. - */ - public Options getOptions() { - return option; - } - - @Override - public String toString() { - switch(option) { - case MATCH_SUPER: - return matcher + " instanceof input"; - case MATCH_DERIVED: - return "input instanceof " + matcher; - case MATCH_EXACT: - return "Exact " + matcher; - default: - throw new IllegalStateException("Unknown option."); - } - } - - @Override - public int hashCode() { - return Objects.hashCode(matcher, option); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } else if (obj instanceof ClassExactMatcher) { - ClassExactMatcher other = (ClassExactMatcher) obj; - - return Objects.equal(matcher, other.matcher) && - Objects.equal(option, other.option); - } - return false; - } -} diff --git a/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassRegexMatcher.java b/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassRegexMatcher.java index a0d88640..cf9cf2de 100644 --- a/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassRegexMatcher.java +++ b/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassRegexMatcher.java @@ -2,57 +2,30 @@ package com.comphenix.protocol.reflect.fuzzy; import java.util.regex.Pattern; -import com.google.common.base.Objects; - /** * Determine if a class matches based on its name using a regular expression. - * + * * @author Kristian */ -class ClassRegexMatcher extends AbstractFuzzyMatcher> { +final class ClassRegexMatcher implements AbstractFuzzyMatcher> { + private final Pattern regex; - private final int priority; - - public ClassRegexMatcher(Pattern regex, int priority) { - if (regex == null) - throw new IllegalArgumentException("Regular expression pattern cannot be NULL."); + + public ClassRegexMatcher(Pattern regex) { this.regex = regex; - this.priority = priority; } @Override public boolean isMatch(Class value, Object parent) { - if (value != null) - return regex.matcher(value.getCanonicalName()).matches(); - else + if (value != null && this.regex != null) { + return this.regex.matcher(value.getCanonicalName()).matches(); + } else { return false; + } } - - @Override - protected int calculateRoundNumber() { - return -priority; - } - + @Override public String toString() { - return "class name of " + regex.toString(); - } - - @Override - public int hashCode() { - return Objects.hashCode(regex, priority); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } else if (obj instanceof ClassRegexMatcher) { - ClassRegexMatcher other = (ClassRegexMatcher) obj; - - return priority == other.priority && - FuzzyMatchers.checkPattern(regex, other.regex); - } - return false; + return "{ type matches \"" + this.regex.pattern() + "\" }"; } } diff --git a/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassSetMatcher.java b/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassSetMatcher.java index b85afb8a..c1b5fa2f 100644 --- a/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassSetMatcher.java +++ b/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassSetMatcher.java @@ -2,56 +2,26 @@ package com.comphenix.protocol.reflect.fuzzy; import java.util.Set; -import com.google.common.base.Objects; - /** * Represents a class matcher that checks for equality using a given set of classes. - * + * * @author Kristian */ -class ClassSetMatcher extends AbstractFuzzyMatcher> { +final class ClassSetMatcher implements AbstractFuzzyMatcher> { + private final Set> classes; - + public ClassSetMatcher(Set> classes) { - if (classes == null) - throw new IllegalArgumentException("Set of classes cannot be NULL."); this.classes = classes; } @Override public boolean isMatch(Class value, Object parent) { - return classes.contains(value); + return this.classes.contains(value); } - - @Override - protected int calculateRoundNumber() { - int roundNumber = 0; - - // The highest round number (except zero). - for (Class clazz : classes) { - roundNumber = combineRounds(roundNumber, -ClassExactMatcher.getClassNumber(clazz)); - } - return roundNumber; - } - + @Override public String toString() { - return "match any: " + classes; - } - - @Override - public int hashCode() { - return classes.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } else if (obj instanceof ClassSetMatcher) { - // See if the sets are equal - return Objects.equal(classes, ((ClassSetMatcher) obj).classes); - } - return true; + return "{ type any of " + this.classes + " }"; } } diff --git a/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassTypeMatcher.java b/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassTypeMatcher.java new file mode 100644 index 00000000..ea7fb08a --- /dev/null +++ b/src/main/java/com/comphenix/protocol/reflect/fuzzy/ClassTypeMatcher.java @@ -0,0 +1,114 @@ +package com.comphenix.protocol.reflect.fuzzy; + +/** + * Used to check class equality. + * + * @author Kristian + */ +final class ClassTypeMatcher implements AbstractFuzzyMatcher> { + + /** + * Match any class. + */ + public static final ClassTypeMatcher MATCH_ALL = new ClassTypeMatcher(null, MatchVariant.MATCH_SUPER); + + private final Class matcher; + private final MatchVariant variant; + + /** + * Constructs a new class matcher. + * + * @param matcher - the matching class, or NULL to represent anything. + * @param variant - options specifying the matching rules. + */ + ClassTypeMatcher(Class matcher, MatchVariant variant) { + this.matcher = matcher; + this.variant = variant; + } + + /** + * Determine if a given class is equivalent. + *

    + * If the matcher is NULL, the result will only be TRUE if we're not matching exactly. + * + * @param input - the input class defined in the source file. + * @param parent - the container that holds a reference to this class. + * @return TRUE if input matches according to the rules in {@link #getMatchVariant()}, FALSE otherwise. + */ + @Override + public boolean isMatch(Class input, Object parent) { + if (input == null) { + throw new IllegalArgumentException("Input class cannot be NULL."); + } + + // if no type to check against is given just ensure that we're not strict checking + if (this.matcher == null) { + return this.variant != MatchVariant.MATCH_EXACT; + } + + switch (this.variant) { + case MATCH_EXACT: + return this.matcher.equals(input); + case MATCH_DERIVED: + return this.matcher.isAssignableFrom(input); + case MATCH_SUPER: + return input.isAssignableFrom(this.matcher); + default: + // unknown option? + return false; + } + } + + /** + * Retrieve the class we're comparing against. + * + * @return Class to compare against. + */ + public Class getMatcher() { + return this.matcher; + } + + /** + * The matching rules for this class matcher. + * + * @return The current matching option. + */ + public MatchVariant getMatchVariant() { + return this.variant; + } + + @Override + public String toString() { + switch (this.variant) { + case MATCH_EXACT: + return "{ type exactly " + this.matcher + " }"; + case MATCH_SUPER: + return "{ type " + this.matcher + " instanceof input }"; + case MATCH_DERIVED: + return "{ type input instanceof " + this.matcher + " }"; + default: + throw new IllegalArgumentException("Unknown match variant " + this.variant); + } + } + + /** + * Different matching rules. + */ + enum MatchVariant { + + /** + * Match classes exactly. + */ + MATCH_EXACT, + + /** + * A match if the input class is a superclass of the matcher class, or the same class. + */ + MATCH_SUPER, + + /** + * A match if the input class is a derived class of the matcher class, or the same class. + */ + MATCH_DERIVED + } +} diff --git a/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyClassContract.java b/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyClassContract.java index 96405451..5f410846 100644 --- a/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyClassContract.java +++ b/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyClassContract.java @@ -1,167 +1,34 @@ package com.comphenix.protocol.reflect.fuzzy; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.MethodInfo; +import com.google.common.collect.ImmutableList; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; - -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.MethodInfo; -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Maps; /** * Determine if a given class implements a given fuzzy (duck typed) contract. - * + * * @author Kristian */ -public class FuzzyClassContract extends AbstractFuzzyMatcher> { +public class FuzzyClassContract implements AbstractFuzzyMatcher> { + private final ImmutableList> fieldContracts; private final ImmutableList> methodContracts; private final ImmutableList> constructorContracts; - + private final ImmutableList>> baseclassContracts; private final ImmutableList>> interfaceContracts; - - /** - * Represents a class contract builder. - * @author Kristian - * - */ - public static class Builder { - private final List> fieldContracts = new ArrayList<>(); - private final List> methodContracts = new ArrayList<>(); - private final List> constructorContracts = new ArrayList<>(); - - private final List>> baseclassContracts = new ArrayList<>(); - private final List>> interfaceContracts = new ArrayList<>(); - - /** - * Add a new field contract. - * @param matcher - new field contract. - * @return This builder, for chaining. - */ - public Builder field(AbstractFuzzyMatcher matcher) { - fieldContracts.add(matcher); - return this; - } - - /** - * Add a new field contract via a builder. - * @param builder - builder for the new field contract. - * @return This builder, for chaining. - */ - public Builder field(FuzzyFieldContract.Builder builder) { - return field(builder.build()); - } - - /** - * Add a new method contract. - * @param matcher - new method contract. - * @return This builder, for chaining. - */ - public Builder method(AbstractFuzzyMatcher matcher) { - methodContracts.add(matcher); - return this; - } - - /** - * Add a new method contract via a builder. - * @param builder - builder for the new method contract. - * @return This builder, for chaining. - */ - public Builder method(FuzzyMethodContract.Builder builder) { - return method(builder.build()); - } - - /** - * Add a new constructor contract. - * @param matcher - new constructor contract. - * @return This builder, for chaining. - */ - public Builder constructor(AbstractFuzzyMatcher matcher) { - constructorContracts.add(matcher); - return this; - } - - /** - * Add a new constructor contract via a builder. - * @param builder - builder for the new constructor contract. - * @return This builder, for chaining. - */ - public Builder constructor(FuzzyMethodContract.Builder builder) { - return constructor(builder.build()); - } - - /** - * Add a new base class contract. - * @param matcher - new base class contract. - * @return This builder, for chaining. - */ - public Builder baseclass(AbstractFuzzyMatcher> matcher) { - baseclassContracts.add(matcher); - return this; - } - - /** - * Add a new base class contract. - * @param builder - builder for the new base class contract. - * @return This builder, for chaining. - */ - public Builder baseclass(FuzzyClassContract.Builder builder) { - return baseclass(builder.build()); - } - - /** - * Add a new interface contract. - * @param matcher - new interface contract. - * @return This builder, for chaining. - */ - public Builder interfaces(AbstractFuzzyMatcher> matcher) { - interfaceContracts.add(matcher); - return this; - } - - /** - * Add a new interface contract. - * @param builder - builder for the new interface contract. - * @return This builder, for chaining. - */ - public Builder interfaces(FuzzyClassContract.Builder builder) { - return interfaces(builder.build()); - } - public FuzzyClassContract build() { - Collections.sort(fieldContracts); - Collections.sort(methodContracts); - Collections.sort(constructorContracts); - Collections.sort(baseclassContracts); - Collections.sort(interfaceContracts); - - // Construct a new class matcher - return new FuzzyClassContract(this); - } - } - - /** - * Construct a new fuzzy class contract builder. - * @return A new builder. - */ - public static Builder newBuilder() { - return new Builder(); - } - /** * Constructs a new fuzzy class contract with the given contracts. + * * @param builder - the builder that is constructing us. */ private FuzzyClassContract(Builder builder) { - super(); this.fieldContracts = ImmutableList.copyOf(builder.fieldContracts); this.methodContracts = ImmutableList.copyOf(builder.methodContracts); this.constructorContracts = ImmutableList.copyOf(builder.constructorContracts); @@ -169,160 +36,267 @@ public class FuzzyClassContract extends AbstractFuzzyMatcher> { this.interfaceContracts = ImmutableList.copyOf(builder.interfaceContracts); } + /** + * Construct a new fuzzy class contract builder. + * + * @return A new builder. + */ + public static Builder newBuilder() { + return new Builder(); + } + /** * Retrieve an immutable list of every field contract. *

    * This list is ordered in descending order of priority. + * * @return List of every field contract. */ public ImmutableList> getFieldContracts() { - return fieldContracts; + return this.fieldContracts; } /** * Retrieve an immutable list of every method contract. *

    * This list is ordered in descending order of priority. + * * @return List of every method contract. */ public ImmutableList> getMethodContracts() { - return methodContracts; + return this.methodContracts; } /** * Retrieve an immutable list of every constructor contract. *

    * This list is ordered in descending order of priority. + * * @return List of every constructor contract. */ public ImmutableList> getConstructorContracts() { - return constructorContracts; + return this.constructorContracts; } - + /** * Retrieve an immutable list of every baseclass contract. *

    * This list is ordered in descending order of priority. + * * @return List of every baseclass contract. */ public ImmutableList>> getBaseclassContracts() { - return baseclassContracts; + return this.baseclassContracts; } - + /** * Retrieve an immutable list of every interface contract. *

    * This list is ordered in descending order of priority. + * * @return List of every interface contract. */ public ImmutableList>> getInterfaceContracts() { - return interfaceContracts; - } - - @Override - protected int calculateRoundNumber() { - // Find the highest round number - return combineRounds(findHighestRound(fieldContracts), - findHighestRound(methodContracts), - findHighestRound(constructorContracts), - findHighestRound(interfaceContracts), - findHighestRound(baseclassContracts)); - } - - private int findHighestRound(Collection> list) { - int highest = 0; - - // Go through all the elements - for (AbstractFuzzyMatcher matcher : list) - highest = combineRounds(highest, matcher.getRoundNumber()); - return highest; + return this.interfaceContracts; } @Override public boolean isMatch(Class value, Object parent) { FuzzyReflection reflection = FuzzyReflection.fromClass(value, true); - + // Make sure all the contracts are valid - return (fieldContracts.size() == 0 || - processContracts(reflection.getFields(), value, fieldContracts)) && - (methodContracts.size() == 0 || - processContracts(MethodInfo.fromMethods(reflection.getMethods()), value, methodContracts)) && - (constructorContracts.size() == 0 || - processContracts(MethodInfo.fromConstructors(value.getDeclaredConstructors()), value, constructorContracts)) && - (baseclassContracts.size() == 0 || - processValue(value.getSuperclass(), parent, baseclassContracts)) && - (interfaceContracts.size() == 0 || - processContracts(Arrays.asList(value.getInterfaces()), parent, interfaceContracts)); + return this.processValue(value.getSuperclass(), parent, this.baseclassContracts) + && this.processContracts(Arrays.asList(value.getInterfaces()), parent, this.interfaceContracts) + && this.processContracts(reflection.getFields(), value, this.fieldContracts) + && this.processContracts(MethodInfo.fromMethods(reflection.getMethods()), value, this.methodContracts) + && this.processContracts(MethodInfo.fromConstructors(value.getDeclaredConstructors()), value, + this.constructorContracts); } private boolean processContracts(Collection values, Object parent, List> matchers) { - boolean[] accepted = new boolean[matchers.size()]; - int count = accepted.length; + // they all match if no values are present + if (values.isEmpty() || matchers.isEmpty()) { + return true; + } - // Process every value in turn + // check if all contracts match the given objects + int acceptingMatchers = 0; for (T value : values) { - int index = processValue(value, parent, accepted, matchers); - - // See if this worked - if (index >= 0) { - accepted[index] = true; - count--; - } - - // Break early - if (count == 0) - return true; - } - return count == 0; - } - - private boolean processValue(T value, Object parent, List> matchers) { - for (int i = 0; i < matchers.size(); i++) { - if (matchers.get(i).isMatch(value, parent)) { - return true; - } - } - - // No match - return false; - } - - private int processValue(T value, Object parent, boolean accepted[], List> matchers) { - // The order matters - for (int i = 0; i < matchers.size(); i++) { - if (!accepted[i]) { - AbstractFuzzyMatcher matcher = matchers.get(i); - - // Mark this as detected - if (matcher.isMatch(value, parent)) { - return i; + if (this.processValue(value, parent, matchers)) { + acceptingMatchers++; + // if all matchers found one match we're done + if (acceptingMatchers == matchers.size()) { + return true; } } } - - // Failure - return -1; + + return false; } - + + private boolean processValue(T value, Object parent, List> matchers) { + // check if all given contracts match the given value + for (AbstractFuzzyMatcher matcher : matchers) { + if (!matcher.isMatch(value, parent)) { + return false; + } + } + + // they all match + return true; + } + @Override public String toString() { - final Map params = new LinkedHashMap<>(); - - if (fieldContracts.size() > 0) { - params.put("fields", fieldContracts); + StringBuilder builder = new StringBuilder("FuzzyClassContract={\n"); + + // append all subcontracts + if (!this.fieldContracts.isEmpty()) { + builder.append(" fields=").append(this.fieldContracts).append("\n"); } - if (methodContracts.size() > 0) { - params.put("methods", methodContracts); + + if (!this.methodContracts.isEmpty()) { + builder.append(" methods=").append(this.methodContracts).append("\n"); } - if (constructorContracts.size() > 0) { - params.put("constructors", constructorContracts); + + if (!this.constructorContracts.isEmpty()) { + builder.append(" constructors=").append(this.constructorContracts).append("\n"); } - if (baseclassContracts.size() > 0) { - params.put("baseclasses", baseclassContracts); + + if (!this.baseclassContracts.isEmpty()) { + builder.append(" baseClasses=").append(this.baseclassContracts).append("\n"); } - if (interfaceContracts.size() > 0) { - params.put("interfaces", interfaceContracts); + + if (!this.interfaceContracts.isEmpty()) { + builder.append(" interfaceClasses=").append(this.interfaceContracts).append("\n"); + } + + // finish off + return builder.append("}").toString(); + } + + /** + * Represents a class contract builder. + * + * @author Kristian + */ + public static final class Builder { + + private final List> fieldContracts = new ArrayList<>(); + private final List> methodContracts = new ArrayList<>(); + private final List> constructorContracts = new ArrayList<>(); + + private final List>> baseclassContracts = new ArrayList<>(); + private final List>> interfaceContracts = new ArrayList<>(); + + /** + * Add a new field contract. + * + * @param matcher - new field contract. + * @return This builder, for chaining. + */ + public Builder field(AbstractFuzzyMatcher matcher) { + this.fieldContracts.add(matcher); + return this; + } + + /** + * Add a new field contract via a builder. + * + * @param builder - builder for the new field contract. + * @return This builder, for chaining. + */ + public Builder field(FuzzyFieldContract.Builder builder) { + return this.field(builder.build()); + } + + /** + * Add a new method contract. + * + * @param matcher - new method contract. + * @return This builder, for chaining. + */ + public Builder method(AbstractFuzzyMatcher matcher) { + this.methodContracts.add(matcher); + return this; + } + + /** + * Add a new method contract via a builder. + * + * @param builder - builder for the new method contract. + * @return This builder, for chaining. + */ + public Builder method(FuzzyMethodContract.Builder builder) { + return this.method(builder.build()); + } + + /** + * Add a new constructor contract. + * + * @param matcher - new constructor contract. + * @return This builder, for chaining. + */ + public Builder constructor(AbstractFuzzyMatcher matcher) { + this.constructorContracts.add(matcher); + return this; + } + + /** + * Add a new constructor contract via a builder. + * + * @param builder - builder for the new constructor contract. + * @return This builder, for chaining. + */ + public Builder constructor(FuzzyMethodContract.Builder builder) { + return this.constructor(builder.build()); + } + + /** + * Add a new base class contract. + * + * @param matcher - new base class contract. + * @return This builder, for chaining. + */ + public Builder baseclass(AbstractFuzzyMatcher> matcher) { + this.baseclassContracts.add(matcher); + return this; + } + + /** + * Add a new base class contract. + * + * @param builder - builder for the new base class contract. + * @return This builder, for chaining. + */ + public Builder baseclass(FuzzyClassContract.Builder builder) { + return this.baseclass(builder.build()); + } + + /** + * Add a new interface contract. + * + * @param matcher - new interface contract. + * @return This builder, for chaining. + */ + public Builder interfaces(AbstractFuzzyMatcher> matcher) { + this.interfaceContracts.add(matcher); + return this; + } + + /** + * Add a new interface contract. + * + * @param builder - builder for the new interface contract. + * @return This builder, for chaining. + */ + public Builder interfaces(FuzzyClassContract.Builder builder) { + return this.interfaces(builder.build()); + } + + public FuzzyClassContract build() { + // Construct a new class matcher + return new FuzzyClassContract(this); } - return "{\n " + Joiner.on(", \n ").join(params.entrySet()) + "\n}"; } } diff --git a/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyFieldContract.java b/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyFieldContract.java index 680c1169..bbf90007 100644 --- a/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyFieldContract.java +++ b/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyFieldContract.java @@ -3,195 +3,174 @@ package com.comphenix.protocol.reflect.fuzzy; import java.lang.reflect.Field; import java.util.Map; import java.util.regex.Pattern; - import javax.annotation.Nonnull; -import com.google.common.base.Objects; - /** * Represents a field matcher. - * + * * @author Kristian */ public class FuzzyFieldContract extends AbstractFuzzyMember { - private AbstractFuzzyMatcher> typeMatcher = ClassExactMatcher.MATCH_ALL; - - /** - * Represents a builder for a field matcher. - * - * @author Kristian - */ - public static class Builder extends AbstractFuzzyMember.Builder { - @Override - public Builder requireModifier(int modifier) { - super.requireModifier(modifier); - return this; - } - - @Override - public Builder banModifier(int modifier) { - super.banModifier(modifier); - return this; - } - - @Override - public Builder requirePublic() { - super.requirePublic(); - return this; - } - - @Override - public Builder nameRegex(String regex) { - super.nameRegex(regex); - return this; - } - - @Override - public Builder nameRegex(Pattern pattern) { - super.nameRegex(pattern); - return this; - } - - @Override - public Builder nameExact(String name) { - super.nameExact(name); - return this; - } - - public Builder declaringClassExactType(Class declaringClass) { - super.declaringClassExactType(declaringClass); - return this; - } - - @Override - public Builder declaringClassSuperOf(Class declaringClass) { - super.declaringClassSuperOf(declaringClass); - return this; - } - - @Override - public Builder declaringClassDerivedOf(Class declaringClass) { - super.declaringClassDerivedOf(declaringClass); - return this; - } - - @Override - public Builder declaringClassMatching(AbstractFuzzyMatcher> classMatcher) { - super.declaringClassMatching(classMatcher); - return this; - } - - @Override - @Nonnull - protected FuzzyFieldContract initialMember() { - return new FuzzyFieldContract(); - } - - public Builder typeExact(Class type) { - member.typeMatcher = FuzzyMatchers.matchExact(type); - return this; - } - - public Builder typeSuperOf(Class type) { - member.typeMatcher = FuzzyMatchers.matchSuper(type); - return this; - } - - public Builder typeDerivedOf(Class type) { - member.typeMatcher = FuzzyMatchers.matchDerived(type); - return this; - } - - public Builder typeMatches(AbstractFuzzyMatcher> matcher) { - member.typeMatcher = matcher; - return this; - } - @Override - public FuzzyFieldContract build() { - member.prepareBuild(); - return new FuzzyFieldContract(member); - } - } + private AbstractFuzzyMatcher> typeMatcher = ClassTypeMatcher.MATCH_ALL; - /** - * Match a field by its type. - * @param matcher - the type to match. - * @return The field contract. - */ - public static FuzzyFieldContract matchType(AbstractFuzzyMatcher> matcher) { - return newBuilder().typeMatches(matcher).build(); - } - - /** - * Return a new fuzzy field contract builder. - * @return New fuzzy field contract builder. - */ - public static Builder newBuilder() { - return new Builder(); - } - private FuzzyFieldContract() { - // Only allow construction through the builder - super(); - } - - /** - * Retrieve the class matcher that matches the type of a field. - * @return The class matcher. - */ - public AbstractFuzzyMatcher> getTypeMatcher() { - return typeMatcher; } /** * Create a new field contract from the given contract. + * * @param other - the contract to clone. */ private FuzzyFieldContract(FuzzyFieldContract other) { super(other); this.typeMatcher = other.typeMatcher; } - + + /** + * Match a field by its type. + * + * @param matcher - the type to match. + * @return The field contract. + */ + public static FuzzyFieldContract matchType(AbstractFuzzyMatcher> matcher) { + return newBuilder().typeMatches(matcher).build(); + } + + /** + * Return a new fuzzy field contract builder. + * + * @return New fuzzy field contract builder. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Retrieve the class matcher that matches the type of a field. + * + * @return The class matcher. + */ + public AbstractFuzzyMatcher> getTypeMatcher() { + return this.typeMatcher; + } + @Override public boolean isMatch(Field value, Object parent) { if (super.isMatch(value, parent)) { - return typeMatcher.isMatch(value.getType(), value); + return this.typeMatcher.isMatch(value.getType(), value); } + // No match return false; } - - @Override - protected int calculateRoundNumber() { - // Combine the two - return combineRounds(super.calculateRoundNumber(), - typeMatcher.calculateRoundNumber()); - } - + @Override protected Map getKeyValueView() { Map member = super.getKeyValueView(); - - if (typeMatcher != ClassExactMatcher.MATCH_ALL) { - member.put("type", typeMatcher); + if (this.typeMatcher != ClassTypeMatcher.MATCH_ALL) { + member.put("type", this.typeMatcher); } + return member; } - - @Override - public int hashCode() { - return Objects.hashCode(typeMatcher, super.hashCode()); - } - - @Override - public boolean equals(Object obj) { - // Use the member equals method - if (this == obj) { - return true; - } else if (obj instanceof FuzzyFieldContract && super.equals(obj)) { - return Objects.equal(typeMatcher, ((FuzzyFieldContract) obj).typeMatcher); + + /** + * Represents a builder for a field matcher. + * + * @author Kristian + */ + public static class Builder extends AbstractFuzzyMember.Builder { + + @Override + public Builder requireModifier(int modifier) { + super.requireModifier(modifier); + return this; + } + + @Override + public Builder banModifier(int modifier) { + super.banModifier(modifier); + return this; + } + + @Override + public Builder requirePublic() { + super.requirePublic(); + return this; + } + + @Override + public Builder nameRegex(String regex) { + super.nameRegex(regex); + return this; + } + + @Override + public Builder nameRegex(Pattern pattern) { + super.nameRegex(pattern); + return this; + } + + @Override + public Builder nameExact(String name) { + super.nameExact(name); + return this; + } + + public Builder declaringClassExactType(Class declaringClass) { + super.declaringClassExactType(declaringClass); + return this; + } + + @Override + public Builder declaringClassSuperOf(Class declaringClass) { + super.declaringClassSuperOf(declaringClass); + return this; + } + + @Override + public Builder declaringClassDerivedOf(Class declaringClass) { + super.declaringClassDerivedOf(declaringClass); + return this; + } + + @Override + public Builder declaringClassMatching(AbstractFuzzyMatcher> classMatcher) { + super.declaringClassMatching(classMatcher); + return this; + } + + @Override + @Nonnull + protected FuzzyFieldContract initialMember() { + return new FuzzyFieldContract(); + } + + public Builder typeExact(Class type) { + this.member.typeMatcher = FuzzyMatchers.matchExact(type); + return this; + } + + public Builder typeSuperOf(Class type) { + this.member.typeMatcher = FuzzyMatchers.matchSuper(type); + return this; + } + + public Builder typeDerivedOf(Class type) { + this.member.typeMatcher = FuzzyMatchers.matchDerived(type); + return this; + } + + public Builder typeMatches(AbstractFuzzyMatcher> matcher) { + this.member.typeMatcher = matcher; + return this; + } + + @Override + public FuzzyFieldContract build() { + this.member.prepareBuild(); + return new FuzzyFieldContract(this.member); } - return true; } } diff --git a/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMatchers.java b/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMatchers.java index b863d949..a68d1e8f 100644 --- a/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMatchers.java +++ b/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMatchers.java @@ -1,86 +1,66 @@ package com.comphenix.protocol.reflect.fuzzy; -import java.lang.reflect.Member; +import com.comphenix.protocol.reflect.fuzzy.ClassTypeMatcher.MatchVariant; +import com.google.common.collect.Sets; import java.util.Set; import java.util.regex.Pattern; -import javax.annotation.Nonnull; - -import com.google.common.base.Preconditions; -import com.google.common.collect.Sets; - /** * Contains factory methods for matching classes. - * + * * @author Kristian */ public class FuzzyMatchers { + // Constant matchers - private static AbstractFuzzyMatcher> MATCH_ALL = new AbstractFuzzyMatcher>() { - @Override - public boolean isMatch(Class value, Object parent) { - return true; - } - - @Override - protected int calculateRoundNumber() { - return 0; - } - }; - + private static final AbstractFuzzyMatcher> MATCH_ALL = (value, parent) -> true; + private FuzzyMatchers() { // Don't make this constructable } /** * Construct a class matcher that matches an array with a given component matcher. + * * @param componentMatcher - the component matcher. * @return A new array matcher. */ - public static AbstractFuzzyMatcher> matchArray(@Nonnull final AbstractFuzzyMatcher> componentMatcher) { - Preconditions.checkNotNull(componentMatcher, "componentMatcher cannot be NULL."); - return new AbstractFuzzyMatcher>() { - @Override - public boolean isMatch(Class value, Object parent) { - return value.isArray() && componentMatcher.isMatch(value.getComponentType(), parent); - } - - @Override - protected int calculateRoundNumber() { - // We're just above object - return -1; - } - }; + public static AbstractFuzzyMatcher> matchArray(AbstractFuzzyMatcher> componentMatcher) { + return (value, parent) -> value.isArray() && componentMatcher.isMatch(value.getComponentType(), parent); } - + /** * Retrieve a fuzzy matcher that will match any class. + * * @return A class matcher. */ public static AbstractFuzzyMatcher> matchAll() { return MATCH_ALL; } - + /** * Construct a class matcher that matches types exactly. + * * @param matcher - the matching class. * @return A new class matcher. */ public static AbstractFuzzyMatcher> matchExact(Class matcher) { - return new ClassExactMatcher(matcher, ClassExactMatcher.Options.MATCH_EXACT); + return new ClassTypeMatcher(matcher, MatchVariant.MATCH_EXACT); } - + /** * Construct a class matcher that matches any of the given classes exactly. + * * @param classes - list of classes to match. * @return A new class matcher. */ public static AbstractFuzzyMatcher> matchAnyOf(Class... classes) { return matchAnyOf(Sets.newHashSet(classes)); } - + /** * Construct a class matcher that matches any of the given classes exactly. + * * @param classes - set of classes to match. * @return A new class matcher. */ @@ -90,98 +70,62 @@ public class FuzzyMatchers { /** * Construct a class matcher that matches super types of the given class. + * * @param matcher - the matching type must be a super class of this type. * @return A new class matcher. */ public static AbstractFuzzyMatcher> matchSuper(Class matcher) { - return new ClassExactMatcher(matcher, ClassExactMatcher.Options.MATCH_SUPER); + return new ClassTypeMatcher(matcher, MatchVariant.MATCH_SUPER); } /** * Construct a class matcher that matches derived types of the given class. + * * @param matcher - the matching type must be a derived class of this type. * @return A new class matcher. */ public static AbstractFuzzyMatcher> matchDerived(Class matcher) { - return new ClassExactMatcher(matcher, ClassExactMatcher.Options.MATCH_DERIVED); - } - - /** - * Construct a class matcher based on the canonical names of classes. - * @param regex - regular expression pattern matching class names. - * @param priority - the priority this matcher takes - higher is better. - * @return A fuzzy class matcher based on name. - */ - public static AbstractFuzzyMatcher> matchRegex(final Pattern regex, final int priority) { - return new ClassRegexMatcher(regex, priority); - } - - /** - * Construct a class matcher based on the canonical names of classes. - * @param regex - regular expression matching class names. - * @param priority - the priority this matcher takes - higher is better. - * @return A fuzzy class matcher based on name. - */ - public static AbstractFuzzyMatcher> matchRegex(String regex, final int priority) { - return FuzzyMatchers.matchRegex(Pattern.compile(regex), priority); + return new ClassTypeMatcher(matcher, MatchVariant.MATCH_DERIVED); } /** - * Match the parent class of a method, field or constructor. - * @return Parent matcher. + * Construct a class matcher based on the canonical names of classes. + * + * @param regex - regular expression pattern matching class names. + * @return A fuzzy class matcher based on name. */ - public static AbstractFuzzyMatcher> matchParent() { - return new AbstractFuzzyMatcher>() { - @Override - public boolean isMatch(Class value, Object parent) { - if (parent instanceof Member) { - return ((Member) parent).getDeclaringClass().equals(value); - } else if (parent instanceof Class) { - return parent.equals(value); - } else { - // Can't be a match - return false; - } - } - - @Override - protected int calculateRoundNumber() { - // We match a very specific type - return -100; - } - - @Override - public String toString() { - return "match parent class"; - } - - @Override - public int hashCode() { - return 0; - } - - @Override - public boolean equals(Object obj) { - // If they're the same type, then yes - return obj != null && obj.getClass() == this.getClass(); - } - }; + public static AbstractFuzzyMatcher> matchRegex(final Pattern regex) { + return new ClassRegexMatcher(regex); } - + /** - * Determine if two patterns are the same. + * Construct a class matcher based on the canonical names of classes. + * + * @param regex - regular expression matching class names. + * @return A fuzzy class matcher based on name. + */ + public static AbstractFuzzyMatcher> matchRegex(String regex) { + return FuzzyMatchers.matchRegex(Pattern.compile(regex)); + } + + /** + * Determine if two patterns are the same. *

    * Note that two patterns may be functionally the same, but nevertheless be different. + * * @param a - the first pattern. * @param b - the second pattern. * @return TRUE if they are compiled from the same pattern, FALSE otherwise. */ static boolean checkPattern(Pattern a, Pattern b) { - if (a == null) + if (a == null) { return b == null; - else if (b == null) + } else if (b == null) { return false; - else + } else if (a == b) { + return true; + } else { return a.pattern().equals(b.pattern()); + } } } diff --git a/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java b/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java index 48e57df0..ce7b0f44 100644 --- a/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java +++ b/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java @@ -1,374 +1,551 @@ package com.comphenix.protocol.reflect.fuzzy; +import com.comphenix.protocol.reflect.MethodInfo; +import com.google.common.collect.ImmutableList; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.regex.Pattern; - import javax.annotation.Nonnull; -import com.comphenix.protocol.reflect.MethodInfo; -import com.google.common.base.Objects; -import com.google.common.collect.ImmutableList; /** * Represents a contract for matching methods or constructors. - * + * * @author Kristian */ public class FuzzyMethodContract extends AbstractFuzzyMember { - private static class ParameterClassMatcher extends AbstractFuzzyMatcher[]> { + + // Match return value + private AbstractFuzzyMatcher> returnMatcher = ClassTypeMatcher.MATCH_ALL; + // Handle parameters and exceptions + private List paramMatchers; + private List exceptionMatchers; + // Expected parameter count + private Integer paramCount; + + private FuzzyMethodContract() { + // Only allow construction from the builder + this.paramMatchers = new ArrayList<>(); + this.exceptionMatchers = new ArrayList<>(); + } + + private FuzzyMethodContract(FuzzyMethodContract other) { + super(other); + this.returnMatcher = other.returnMatcher; + this.paramMatchers = other.paramMatchers; + this.exceptionMatchers = other.exceptionMatchers; + this.paramCount = other.paramCount; + } + + /** + * Return a method contract builder. + * + * @return Method contract builder. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Construct a new immutable copy of the given method contract. + * + * @param other - the contract to clone. + * @return A immutable copy of the given contract. + */ + private static FuzzyMethodContract immutableCopy(FuzzyMethodContract other) { + FuzzyMethodContract copy = new FuzzyMethodContract(other); + + // Ensure that the lists are immutable + copy.paramMatchers = ImmutableList.copyOf(copy.paramMatchers); + copy.exceptionMatchers = ImmutableList.copyOf(copy.exceptionMatchers); + return copy; + } + + /** + * Retrieve the class matcher for the return type. + * + * @return Class matcher for the return type. + */ + public AbstractFuzzyMatcher> getReturnMatcher() { + return this.returnMatcher; + } + + /** + * Retrieve an immutable list of every parameter matcher for this method. + * + * @return Immutable list of every parameter matcher. + */ + public ImmutableList getParamMatchers() { + if (this.paramMatchers instanceof ImmutableList) { + return (ImmutableList) this.paramMatchers; + } else { + throw new IllegalStateException("Lists haven't been sealed yet."); + } + } + + /** + * Retrieve an immutable list of every exception matcher for this method. + * + * @return Immutable list of every exception matcher. + */ + public List getExceptionMatchers() { + if (this.exceptionMatchers instanceof ImmutableList) { + return this.exceptionMatchers; + } else { + throw new IllegalStateException("Lists haven't been sealed yet."); + } + } + + /** + * Retrieve the expected parameter count for this method. + * + * @return Expected parameter count, or NULL if anyting goes. + */ + public Integer getParamCount() { + return this.paramCount; + } + + @Override + public boolean isMatch(MethodInfo value, Object parent) { + if (super.isMatch(value, parent)) { + // check the return type first (the easiest check) + if (!this.returnMatcher.isMatch(value.getReturnType(), value)) { + return false; + } + + // check for the parameter types + Class[] params = value.getParameterTypes(); + if (this.paramCount != null && this.paramCount != params.length) { + return false; + } + + // check parameters and exceptions + return this.matchTypes(params, value, this.paramMatchers) + && this.matchTypes(value.getExceptionTypes(), value, this.exceptionMatchers); + } + + // No match + return false; + } + + private boolean matchTypes(Class[] types, MethodInfo parent, List matchers) { + if (matchers.isEmpty()) { + // no matchers - no show + return true; + } + + // the amount of matchers which are ok with the parameter types + int acceptingMatchers = 0; + for (int i = 0; i < types.length; i++) { + if (this.processValue(types[i], parent, i, matchers)) { + acceptingMatchers++; + // if all matchers accepted one type we are done + if (acceptingMatchers == matchers.size()) { + return true; + } + } + } + + return false; + } + + private boolean processValue(Class value, MethodInfo parent, int index, List matchers) { + // The order matters + for (ParameterClassMatcher matcher : matchers) { + // See if we got jackpot + if (matcher.isParameterMatch(value, parent, index)) { + return true; + } + } + + // Failure + return false; + } + + @Override + protected Map getKeyValueView() { + Map member = super.getKeyValueView(); + + // Only add fields that are actual constraints + if (this.returnMatcher != ClassTypeMatcher.MATCH_ALL) { + member.put("return", this.returnMatcher); + } + + if (!this.paramMatchers.isEmpty()) { + member.put("params", this.paramMatchers); + } + + if (!this.exceptionMatchers.isEmpty()) { + member.put("exceptions", this.exceptionMatchers); + } + + if (this.paramCount != null) { + member.put("paramCount", this.paramCount); + } + + return member; + } + + private static final class ParameterClassMatcher implements AbstractFuzzyMatcher[]> { + /** * The expected index. */ private final AbstractFuzzyMatcher> typeMatcher; private final Integer indexMatch; - + /** * Construct a new parameter class matcher. + * * @param typeMatcher - class type matcher. */ public ParameterClassMatcher(@Nonnull AbstractFuzzyMatcher> typeMatcher) { this(typeMatcher, null); } - + /** * Construct a new parameter class matcher. + * * @param typeMatcher - class type matcher. - * @param indexMatch - parameter index to match, or NULL for anything. + * @param indexMatch - parameter index to match, or NULL for anything. */ public ParameterClassMatcher(@Nonnull AbstractFuzzyMatcher> typeMatcher, Integer indexMatch) { - if (typeMatcher == null) - throw new IllegalArgumentException("Type matcher cannot be NULL."); - this.typeMatcher = typeMatcher; this.indexMatch = indexMatch; } - + /** * See if there's a match for this matcher. - * @param used - parameters that have been matched before. + * + * @param param - the type to match. * @param parent - the container (member) that holds a reference to this parameter. - * @param params - the type of each parameter. + * @param index - the index of the current parameter. * @return TRUE if this matcher matches any of the given parameters, FALSE otherwise. */ public boolean isParameterMatch(Class param, MethodInfo parent, int index) { // Make sure the index is valid (or NULL) - if (indexMatch == null || indexMatch == index) - return typeMatcher.isMatch(param, parent); - else + if (this.indexMatch == null || this.indexMatch == index) { + return this.typeMatcher.isMatch(param, parent); + } else { return false; + } } @Override public boolean isMatch(Class[] value, Object parent) { - throw new UnsupportedOperationException("Use the parameter match instead."); + throw new UnsupportedOperationException(); } - @Override - protected int calculateRoundNumber() { - return typeMatcher.getRoundNumber(); - } - @Override public String toString() { - return String.format("{Type: %s, Index: %s}", typeMatcher, indexMatch); + return String.format("{ Parameter Type: %s, Index: %s }", this.typeMatcher, this.indexMatch); } } - - // Match return value - private AbstractFuzzyMatcher> returnMatcher = ClassExactMatcher.MATCH_ALL; - - // Handle parameters and exceptions - private List paramMatchers; - private List exceptionMatchers; - - // Expected parameter count - private Integer paramCount; - + /** * Represents a builder for a fuzzy method contract. - * + * * @author Kristian */ - public static class Builder extends AbstractFuzzyMember.Builder { + public static final class Builder extends AbstractFuzzyMember.Builder { + @Override public Builder requireModifier(int modifier) { super.requireModifier(modifier); return this; } - + @Override public Builder requirePublic() { super.requirePublic(); return this; } - + @Override public Builder banModifier(int modifier) { super.banModifier(modifier); return this; } - + @Override public Builder nameRegex(String regex) { super.nameRegex(regex); return this; } - + @Override public Builder nameRegex(Pattern pattern) { super.nameRegex(pattern); return this; } - + @Override public Builder nameExact(String name) { super.nameExact(name); return this; } - + @Override public Builder declaringClassExactType(Class declaringClass) { super.declaringClassExactType(declaringClass); return this; } - + @Override public Builder declaringClassSuperOf(Class declaringClass) { super.declaringClassSuperOf(declaringClass); return this; } - + @Override public Builder declaringClassDerivedOf(Class declaringClass) { super.declaringClassDerivedOf(declaringClass); return this; } - + @Override public Builder declaringClassMatching(AbstractFuzzyMatcher> classMatcher) { super.declaringClassMatching(classMatcher); return this; } - + /** * Add a new required parameter by type for any matching method. + * * @param type - the exact type this parameter must match. * @return This builder, for chaining. */ public Builder parameterExactType(Class type) { - member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type))); + this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type))); return this; } - + /** * Add a new required parameter whose type must be a superclass of the given type. *

    * If a method parameter is of type Number, then any derived class here (Integer, Long, etc.) will match it. + * * @param type - a type or less derived type of the matching parameter. * @return This builder, for chaining. */ public Builder parameterSuperOf(Class type) { - member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type))); + this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type))); return this; } - + /** * Add a new required parameter whose type must be a derived class of the given class. *

    * If the method parameter has the type Integer, then the class Number here will match it. + * * @param type - a type or more derived type of the matching parameter. * @return This builder, for chaining. */ public Builder parameterDerivedOf(Class type) { - member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type))); + this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type))); return this; } /** * Add a new required parameter whose type must match the given class matcher. + * * @param classMatcher - the class matcher. * @return This builder, for chaining. */ public Builder parameterMatches(AbstractFuzzyMatcher> classMatcher) { - member.paramMatchers.add(new ParameterClassMatcher(classMatcher)); + this.member.paramMatchers.add(new ParameterClassMatcher(classMatcher)); return this; } - + /** * Add a new required parameter by type and position for any matching method. - * @param type - the exact type this parameter must match. + * + * @param type - the exact type this parameter must match. * @param index - the expected position in the parameter list. * @return This builder, for chaining. */ public Builder parameterExactType(Class type, int index) { - member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type), index)); + this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type), index)); return this; } - + /** * Add a new required parameters by type and order for any matching method. + * * @param types - the types of every parameters in order. * @return This builder, for chaining. */ public Builder parameterExactArray(Class... types) { - parameterCount(types.length); - + this.parameterCount(types.length); for (int i = 0; i < types.length; i++) { - parameterExactType(types[i], i); + this.parameterExactType(types[i], i); } + return this; } - + /** * Add a new required parameter whose type must be a superclass of the given type. *

    * If a parameter is of type Number, any derived class (Integer, Long, etc.) will match it. - * @param type - a type or derived type of the matching parameter. + * + * @param type - a type or derived type of the matching parameter. * @param index - the expected position in the parameter list. * @return This builder, for chaining. */ public Builder parameterSuperOf(Class type, int index) { - member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type), index)); + this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type), index)); return this; } - + /** * Add a new required parameter whose type must be a derived class of the given class. *

    * If the method parameter has the type Integer, then the class Number here will match it. - * @param type - a type or more derived type of the matching parameter. + * + * @param type - a type or more derived type of the matching parameter. * @param index - the expected position in the parameter list. * @return This builder, for chaining. */ public Builder parameterDerivedOf(Class type, int index) { - member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type), index)); + this.member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type), index)); return this; } - + /** * Add a new required parameter whose type must match the given class matcher and index. + * * @param classMatcher - the class matcher. - * @param index - the expected position in the parameter list. + * @param index - the expected position in the parameter list. * @return This builder, for chaining. */ public Builder parameterMatches(AbstractFuzzyMatcher> classMatcher, int index) { - member.paramMatchers.add(new ParameterClassMatcher(classMatcher, index)); + this.member.paramMatchers.add(new ParameterClassMatcher(classMatcher, index)); return this; } - + /** * Set the expected number of parameters in the matching method. + * * @param expectedCount - the number of parameters to expect. * @return This builder, for chaining. */ public Builder parameterCount(int expectedCount) { - member.paramCount = expectedCount; + this.member.paramCount = expectedCount; return this; } - + /** * Require a void method. + * * @return This builder, for chaining. */ public Builder returnTypeVoid() { - return returnTypeExact(Void.TYPE); + return this.returnTypeExact(Void.TYPE); } - + /** * Set the return type of a matching method exactly. + * * @param type - the exact return type. * @return This builder, for chaining. */ public Builder returnTypeExact(Class type) { - member.returnMatcher = FuzzyMatchers.matchExact(type); + this.member.returnMatcher = FuzzyMatchers.matchExact(type); return this; } - + /** * Set the expected super class of the return type for every matching method. + * * @param type - the return type, or a super class of it. * @return This builder, for chaining. */ public Builder returnDerivedOf(Class type) { - member.returnMatcher = FuzzyMatchers.matchDerived(type); + this.member.returnMatcher = FuzzyMatchers.matchDerived(type); return this; } - + /** * Set a matcher that must match the return type of a matching method. + * * @param classMatcher - the exact return type. * @return This builder, for chaining. */ public Builder returnTypeMatches(AbstractFuzzyMatcher> classMatcher) { - member.returnMatcher = classMatcher; + this.member.returnMatcher = classMatcher; return this; } - + /** * Add a throwable exception that must match the given type exactly. + * * @param type - exception type. * @return This builder, for chaining. */ public Builder exceptionExactType(Class type) { - member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type))); + this.member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type))); return this; } - + /** * Add a throwable exception that must match the given type or be derived. + * * @param type - exception type. * @return This builder, for chaining. */ public Builder exceptionSuperOf(Class type) { - member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type))); + this.member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type))); return this; } - + /** * Add a throwable exception that must match the given matcher, + * * @param classMatcher - the class matcher that must match. * @return This builder, for chaining. */ public Builder exceptionMatches(AbstractFuzzyMatcher> classMatcher) { - member.exceptionMatchers.add(new ParameterClassMatcher(classMatcher)); + this.member.exceptionMatchers.add(new ParameterClassMatcher(classMatcher)); return this; } - + /** * Add a throwable exception that must match the given type exactly and index. - * @param type - exception type. + * + * @param type - exception type. * @param index - the position in the throwable list. * @return This builder, for chaining. */ public Builder exceptionExactType(Class type, int index) { - member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type), index)); + this.member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchExact(type), index)); return this; } - + /** * Add a throwable exception that must match the given type or be derived and index. - * @param type - exception type. + * + * @param type - exception type. * @param index - the position in the throwable list. * @return This builder, for chaining. */ public Builder exceptionSuperOf(Class type, int index) { - member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type), index)); + this.member.exceptionMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type), index)); return this; } - + /** * Add a throwable exception that must match the given matcher and index. + * * @param classMatcher - the class matcher that must match. - * @param index - the position in the throwable list. + * @param index - the position in the throwable list. * @return This builder, for chaining. */ public Builder exceptionMatches(AbstractFuzzyMatcher> classMatcher, int index) { - member.exceptionMatchers.add(new ParameterClassMatcher(classMatcher, index)); + this.member.exceptionMatchers.add(new ParameterClassMatcher(classMatcher, index)); return this; } - + @Override @Nonnull protected FuzzyMethodContract initialMember() { @@ -378,205 +555,8 @@ public class FuzzyMethodContract extends AbstractFuzzyMember { @Override public FuzzyMethodContract build() { - member.prepareBuild(); - return immutableCopy(member); + this.member.prepareBuild(); + return immutableCopy(this.member); } } - - /** - * Return a method contract builder. - * @return Method contract builder. - */ - public static Builder newBuilder() { - return new Builder(); - } - - private FuzzyMethodContract() { - // Only allow construction from the builder - paramMatchers = new ArrayList<>(); - exceptionMatchers = new ArrayList<>(); - } - - private FuzzyMethodContract(FuzzyMethodContract other) { - super(other); - this.returnMatcher = other.returnMatcher; - this.paramMatchers = other.paramMatchers; - this.exceptionMatchers = other.exceptionMatchers; - this.paramCount = other.paramCount; - } - - /** - * Construct a new immutable copy of the given method contract. - * @param other - the contract to clone. - * @return A immutable copy of the given contract. - */ - private static FuzzyMethodContract immutableCopy(FuzzyMethodContract other) { - FuzzyMethodContract copy = new FuzzyMethodContract(other); - - // Ensure that the lists are immutable - copy.paramMatchers = ImmutableList.copyOf(copy.paramMatchers); - copy.exceptionMatchers = ImmutableList.copyOf(copy.exceptionMatchers); - return copy; - } - - /** - * Retrieve the class matcher for the return type. - * @return Class matcher for the return type. - */ - public AbstractFuzzyMatcher> getReturnMatcher() { - return returnMatcher; - } - - /** - * Retrieve an immutable list of every parameter matcher for this method. - * @return Immutable list of every parameter matcher. - */ - public ImmutableList getParamMatchers() { - if (paramMatchers instanceof ImmutableList) - return (ImmutableList) paramMatchers; - else - throw new IllegalStateException("Lists haven't been sealed yet."); - } - - /** - * Retrieve an immutable list of every exception matcher for this method. - * @return Immutable list of every exception matcher. - */ - public List getExceptionMatchers() { - if (exceptionMatchers instanceof ImmutableList) - return exceptionMatchers; - else - throw new IllegalStateException("Lists haven't been sealed yet."); - } - - /** - * Retrieve the expected parameter count for this method. - * @return Expected parameter count, or NULL if anyting goes. - */ - public Integer getParamCount() { - return paramCount; - } - - @Override - protected void prepareBuild() { - super.prepareBuild(); - - // Sort lists such that more specific tests are up front - Collections.sort(paramMatchers); - Collections.sort(exceptionMatchers); - } - - @Override - public boolean isMatch(MethodInfo value, Object parent) { - if (super.isMatch(value, parent)) { - Class[] params = value.getParameterTypes(); - Class[] exceptions = value.getExceptionTypes(); - - if (!returnMatcher.isMatch(value.getReturnType(), value)) - return false; - if (paramCount != null && paramCount != value.getParameterTypes().length) - return false; - - // Finally, check parameters and exceptions - return matchParameters(params, value, paramMatchers) && - matchParameters(exceptions, value, exceptionMatchers); - } - // No match - return false; - } - - private boolean matchParameters(Class[] types, MethodInfo parent, List matchers) { - boolean[] accepted = new boolean[matchers.size()]; - int count = accepted.length; - - // Process every parameter in turn - for (int i = 0; i < types.length; i++) { - int matcherIndex = processValue(types[i], parent, i, accepted, matchers); - - if (matcherIndex >= 0) { - accepted[matcherIndex] = true; - count--; - } - - // Break early - if (count == 0) - return true; - } - return count == 0; - } - - private int processValue(Class value, MethodInfo parent, int index, boolean accepted[], List matchers) { - // The order matters - for (int i = 0; i < matchers.size(); i++) { - if (!accepted[i]) { - // See if we got jackpot - if (matchers.get(i).isParameterMatch(value, parent, index)) { - return i; - } - } - } - - // Failure - return -1; - } - - @Override - protected int calculateRoundNumber() { - int current = 0; - - // Consider the return value first - current = returnMatcher.getRoundNumber(); - - // Handle parameters - for (ParameterClassMatcher matcher : paramMatchers) { - current = combineRounds(current, matcher.calculateRoundNumber()); - } - // And exceptions - for (ParameterClassMatcher matcher : exceptionMatchers) { - current = combineRounds(current, matcher.calculateRoundNumber()); - } - - return combineRounds(super.calculateRoundNumber(), current); - } - - @Override - protected Map getKeyValueView() { - Map member = super.getKeyValueView(); - - // Only add fields that are actual contraints - if (returnMatcher != ClassExactMatcher.MATCH_ALL) { - member.put("return", returnMatcher); - } - if (paramMatchers.size() > 0) { - member.put("params", paramMatchers); - } - if (exceptionMatchers.size() > 0) { - member.put("exceptions", exceptionMatchers); - } - if (paramCount != null) { - member.put("paramCount", paramCount); - } - return member; - } - - @Override - public int hashCode() { - return Objects.hashCode(returnMatcher, paramMatchers, exceptionMatchers, paramCount, super.hashCode()); - } - - @Override - public boolean equals(Object obj) { - // Use the member equals method - if (this == obj) { - return true; - } else if (obj instanceof FuzzyMethodContract && super.equals(obj)) { - FuzzyMethodContract other = (FuzzyMethodContract) obj; - - return Objects.equal(paramCount, other.paramCount) && - Objects.equal(returnMatcher, other.returnMatcher) && - Objects.equal(paramMatchers, other.paramMatchers) && - Objects.equal(exceptionMatchers, other.exceptionMatchers); - } - return true; - } } diff --git a/src/main/java/com/comphenix/protocol/reflect/instances/ExistingGenerator.java b/src/main/java/com/comphenix/protocol/reflect/instances/ExistingGenerator.java index 386c9fdf..96d8c5eb 100644 --- a/src/main/java/com/comphenix/protocol/reflect/instances/ExistingGenerator.java +++ b/src/main/java/com/comphenix/protocol/reflect/instances/ExistingGenerator.java @@ -2,30 +2,29 @@ * 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 + * 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. + * 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 + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ package com.comphenix.protocol.reflect.instances; -import com.comphenix.protocol.reflect.FieldUtils; -import com.comphenix.protocol.reflect.FuzzyReflection; - -import javax.annotation.Nullable; +import com.comphenix.protocol.reflect.accessors.Accessors; import java.lang.reflect.Field; -import java.util.ArrayDeque; import java.util.Collection; +import java.util.ArrayDeque; import java.util.HashMap; import java.util.Map; +import javax.annotation.Nullable; +import com.comphenix.protocol.reflect.FuzzyReflection; /** * Provides instance constructors using a list of existing values. @@ -36,7 +35,7 @@ import java.util.Map; public class ExistingGenerator implements InstanceProvider { /** * Represents a single node in the tree of possible values. - * + * * @author Kristian */ private static final class Node { @@ -44,7 +43,7 @@ public class ExistingGenerator implements InstanceProvider { 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; @@ -56,7 +55,7 @@ public class ExistingGenerator implements InstanceProvider { children.put(node.key, node); return node; } - + public int getLevel() { return level; } @@ -64,7 +63,7 @@ public class ExistingGenerator implements InstanceProvider { public Collection getChildren() { return children.values(); } - + public Object getValue() { return value; } @@ -72,19 +71,19 @@ public class ExistingGenerator implements InstanceProvider { 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 } - + /** * Automatically create an instance provider from a objects public and private fields. *

    @@ -96,10 +95,10 @@ public class ExistingGenerator implements InstanceProvider { public static ExistingGenerator fromObjectFields(Object object) { if (object == null) throw new IllegalArgumentException("Object cannot be NULL."); - + return fromObjectFields(object, object.getClass()); } - + /** * Automatically create an instance provider from a objects public and private fields. *

    @@ -111,7 +110,7 @@ public class ExistingGenerator implements InstanceProvider { */ public static ExistingGenerator fromObjectFields(Object object, Class type) { ExistingGenerator generator = new ExistingGenerator(); - + // Possible errors if (object == null) throw new IllegalArgumentException("Object cannot be NULL."); @@ -119,16 +118,16 @@ public class ExistingGenerator implements InstanceProvider { throw new IllegalArgumentException("Type cannot be NULL."); if (!type.isAssignableFrom(object.getClass())) throw new IllegalArgumentException("Type must be a superclass or be the same type."); - + // Read instances from every field. for (Field field : FuzzyReflection.fromClass(type, true).getFields()) { try { - Object value = FieldUtils.readField(field, object, true); + Object value = Accessors.getFieldAccessor(field).get(object); // Use the type of the field, not the object itself if (value != null) generator.addObject(field.getType(), value); - + } catch (Exception e) { // Yes, swallow it. No, really. } @@ -136,7 +135,7 @@ public class ExistingGenerator implements InstanceProvider { return generator; } - + /** * Create an instance generator from a pre-defined array of values. * @param values - values to provide. @@ -144,34 +143,34 @@ public class ExistingGenerator implements InstanceProvider { */ public static ExistingGenerator fromObjectArray(Object[] values) { ExistingGenerator generator = new ExistingGenerator(); - + for (Object value : values) generator.addObject(value); - + return generator; } - + private void addObject(Object value) { if (value == null) throw new IllegalArgumentException("Value cannot be NULL."); - + 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; @@ -180,19 +179,19 @@ public class ExistingGenerator implements InstanceProvider { 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()) { @@ -201,23 +200,23 @@ public class ExistingGenerator implements InstanceProvider { } 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) { final ArrayDeque> result = new ArrayDeque<>(); @@ -233,12 +232,12 @@ public class ExistingGenerator implements InstanceProvider { public Object create(@Nullable Class type) { // 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 if (node != null) return node.getValue(); diff --git a/src/main/java/com/comphenix/protocol/reflect/instances/MinecraftGenerator.java b/src/main/java/com/comphenix/protocol/reflect/instances/MinecraftGenerator.java index c4a9c192..d067376b 100644 --- a/src/main/java/com/comphenix/protocol/reflect/instances/MinecraftGenerator.java +++ b/src/main/java/com/comphenix/protocol/reflect/instances/MinecraftGenerator.java @@ -1,77 +1,81 @@ package com.comphenix.protocol.reflect.instances; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.wrappers.BukkitConverters; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; import org.bukkit.Material; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; -import java.lang.reflect.Constructor; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - public class MinecraftGenerator { - // system unique id representation - public static final UUID SYS_UUID; - // minecraft default types - public static final Object AIR_ITEM_STACK; - private static Object DEFAULT_ENTITY_TYPES; // modern servers only (older servers will use an entity type id) - // minecraft method accessors - private static final MethodAccessor NON_NULL_LIST_CREATE; - // fast util mappings for paper relocation - private static final Map, Constructor> FAST_MAP_CONSTRUCTORS; - static { - try { - SYS_UUID = new UUID(0L, 0L); - AIR_ITEM_STACK = BukkitConverters.getItemStackConverter().getGeneric(new ItemStack(Material.AIR)); - FAST_MAP_CONSTRUCTORS = new ConcurrentHashMap<>(); - NON_NULL_LIST_CREATE = MinecraftReflection.getNonNullListCreateAccessor(); - } catch (Throwable ex) { - throw new RuntimeException("Failed to create static fields in MinecraftGenerator", ex); - } - } + // system unique id representation + public static final UUID SYS_UUID; + // minecraft default types + public static final Object AIR_ITEM_STACK; + private static Object DEFAULT_ENTITY_TYPES; // modern servers only (older servers will use an entity type id) + // minecraft method accessors + private static final MethodAccessor NON_NULL_LIST_CREATE; + // fast util mappings for paper relocation + private static final Map, ConstructorAccessor> FAST_MAP_CONSTRUCTORS; - public static final InstanceProvider INSTANCE = type -> { - if (type != null) { - if (type == UUID.class) { - return SYS_UUID; - } else if (type.isEnum()) { - return type.getEnumConstants()[0]; - } else if (type == MinecraftReflection.getItemStackClass()) { - return AIR_ITEM_STACK; - } else if (type == MinecraftReflection.getEntityTypes()) { - if (DEFAULT_ENTITY_TYPES == null) { - // try to initialize now - try { - DEFAULT_ENTITY_TYPES = BukkitConverters.getEntityTypeConverter().getGeneric(EntityType.AREA_EFFECT_CLOUD); - } catch (Exception ignored) { - // not available in this version of minecraft - } - } - return DEFAULT_ENTITY_TYPES; - } else if (type.isAssignableFrom(Map.class)) { - Constructor ctor = FAST_MAP_CONSTRUCTORS.computeIfAbsent(type, __ -> { - try { - String name = type.getCanonicalName(); - if (name != null && name.contains("it.unimi.fastutils")) { - return Class.forName(name.substring(name.length() - 3) + "OpenHashMap").getConstructor(); - } - } catch (Exception ignored) {} - return null; - }); - if (ctor != null) { - try { - return ctor.newInstance(); - } catch (ReflectiveOperationException ignored) {} - } - } else if (NON_NULL_LIST_CREATE != null && type == MinecraftReflection.getNonNullListClass()) { - return NON_NULL_LIST_CREATE.invoke(null); - } - } + static { + try { + SYS_UUID = new UUID(0L, 0L); + AIR_ITEM_STACK = BukkitConverters.getItemStackConverter().getGeneric(new ItemStack(Material.AIR)); + FAST_MAP_CONSTRUCTORS = new HashMap<>(); + NON_NULL_LIST_CREATE = MinecraftReflection.getNonNullListCreateAccessor(); + } catch (Throwable ex) { + throw new RuntimeException("Failed to create static fields in MinecraftGenerator", ex); + } + } - return null; - }; + public static final InstanceProvider INSTANCE = type -> { + if (type != null) { + if (type == UUID.class) { + return SYS_UUID; + } else if (type.isEnum()) { + return type.getEnumConstants()[0]; + } else if (type == MinecraftReflection.getItemStackClass()) { + return AIR_ITEM_STACK; + } else if (type == MinecraftReflection.getEntityTypes()) { + if (DEFAULT_ENTITY_TYPES == null) { + // try to initialize now + try { + DEFAULT_ENTITY_TYPES = BukkitConverters.getEntityTypeConverter().getGeneric(EntityType.AREA_EFFECT_CLOUD); + } catch (Exception ignored) { + // not available in this version of minecraft + } + } + return DEFAULT_ENTITY_TYPES; + } else if (type.isAssignableFrom(Map.class)) { + ConstructorAccessor ctor = FAST_MAP_CONSTRUCTORS.computeIfAbsent(type, __ -> { + try { + String name = type.getCanonicalName(); + if (name != null && name.contains("it.unimi.fastutils")) { + Class clz = Class.forName(name.substring(name.length() - 3) + "OpenHashMap"); + return Accessors.getConstructorAccessorOrNull(clz); + } + } catch (Exception ignored) { + } + return null; + }); + if (ctor != null) { + try { + return ctor.invoke(); + } catch (Exception ignored) { + } + } + } else if (NON_NULL_LIST_CREATE != null && type == MinecraftReflection.getNonNullListClass()) { + return NON_NULL_LIST_CREATE.invoke(null); + } + } + + return null; + }; } diff --git a/src/main/java/com/comphenix/protocol/utility/ByteBuddyFactory.java b/src/main/java/com/comphenix/protocol/utility/ByteBuddyFactory.java index 6d2404eb..96bc9431 100644 --- a/src/main/java/com/comphenix/protocol/utility/ByteBuddyFactory.java +++ b/src/main/java/com/comphenix/protocol/utility/ByteBuddyFactory.java @@ -1,48 +1,51 @@ package com.comphenix.protocol.utility; import net.bytebuddy.ByteBuddy; -import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; /** * Represents a shared ByteBuddy factory. + * * @author Kristian */ -public class ByteBuddyFactory { - private static ByteBuddyFactory INSTANCE = new ByteBuddyFactory(); - +public final class ByteBuddyFactory { + + private static final ByteBuddyFactory INSTANCE = new ByteBuddyFactory(); + // The current class loader private ClassLoader loader = ByteBuddyFactory.class.getClassLoader(); - + public static ByteBuddyFactory getInstance() { return INSTANCE; } - + + /** + * Get the current class loader we are using. + * + * @return The current class loader. + */ + public ClassLoader getClassLoader() { + return this.loader; + } + /** * Set the current class loader to use when constructing enhancers. + * * @param loader - the class loader */ public void setClassLoader(ClassLoader loader) { this.loader = loader; } - - /** - * Get the current class loader we are using. - * @return The current class loader. - */ - public ClassLoader getClassLoader() { - return loader; - } /** * Creates a type builder for a subclass of a given {@link Class}. + * * @param clz The class for which to create a subclass. - * @return A type builder for creating a new class extending the provided clz and implementing - * {@link ByteBuddyGenerated}. + * @return A type builder for creating a new class extending the provided clz and implementing {@link + * ByteBuddyGenerated}. */ - public DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional createSubclass(Class clz) - { + public DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional createSubclass(Class clz) { return new ByteBuddy() .subclass(clz) .implement(ByteBuddyGenerated.class); @@ -50,14 +53,16 @@ public class ByteBuddyFactory { /** * Creates a type builder for a subclass of a given {@link Class}. - * @param clz The class for which to create a subclass. + * + * @param clz The class for which to create a subclass. * @param constructorStrategy The constructor strategy to use. - * @return A type builder for creating a new class extending the provided clz and implementing - * {@link ByteBuddyGenerated}. + * @return A type builder for creating a new class extending the provided clz and implementing {@link + * ByteBuddyGenerated}. */ - public DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional createSubclass(Class clz, - ConstructorStrategy.Default constructorStrategy) - { + public DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional createSubclass( + Class clz, + ConstructorStrategy.Default constructorStrategy + ) { return new ByteBuddy() .subclass(clz, constructorStrategy) .implement(ByteBuddyGenerated.class); diff --git a/src/main/java/com/comphenix/protocol/utility/ByteBuddyGenerated.java b/src/main/java/com/comphenix/protocol/utility/ByteBuddyGenerated.java index b531d306..f556599e 100644 --- a/src/main/java/com/comphenix/protocol/utility/ByteBuddyGenerated.java +++ b/src/main/java/com/comphenix/protocol/utility/ByteBuddyGenerated.java @@ -6,4 +6,5 @@ package com.comphenix.protocol.utility; * @author Pim */ public interface ByteBuddyGenerated { + } diff --git a/src/main/java/com/comphenix/protocol/utility/ByteBufferInputStream.java b/src/main/java/com/comphenix/protocol/utility/ByteBufferInputStream.java deleted file mode 100644 index ce730f12..00000000 --- a/src/main/java/com/comphenix/protocol/utility/ByteBufferInputStream.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.comphenix.protocol.utility; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; - -/** - * Represents an input stream that delegates to a byte buffer. - * @author Kristian - */ -public class ByteBufferInputStream extends InputStream { - private ByteBuffer buf; - - public ByteBufferInputStream(ByteBuffer buf) { - this.buf = buf; - } - - public int read() throws IOException { - if (!buf.hasRemaining()) { - return -1; - } - return buf.get() & 0xFF; - } - - public int read(byte[] bytes, int off, int len) - throws IOException { - if (!buf.hasRemaining()) { - return -1; - } - - len = Math.min(len, buf.remaining()); - buf.get(bytes, off, len); - return len; - } -} diff --git a/src/main/java/com/comphenix/protocol/utility/ByteBufferOutputStream.java b/src/main/java/com/comphenix/protocol/utility/ByteBufferOutputStream.java deleted file mode 100644 index 89d37879..00000000 --- a/src/main/java/com/comphenix/protocol/utility/ByteBufferOutputStream.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.comphenix.protocol.utility; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * Represents an output stream that is backed by a ByteBuffer. - * @author Kristian - */ -public class ByteBufferOutputStream extends OutputStream { - ByteBuffer buf; - - public ByteBufferOutputStream(ByteBuffer buf) { - this.buf = buf; - } - - public void write(int b) throws IOException { - buf.put((byte) b); - } - - public void write(byte[] bytes, int off, int len) - throws IOException { - buf.put(bytes, off, len); - } -} diff --git a/src/main/java/com/comphenix/protocol/utility/CachedPackage.java b/src/main/java/com/comphenix/protocol/utility/CachedPackage.java index 7efed35b..8bd33ab2 100644 --- a/src/main/java/com/comphenix/protocol/utility/CachedPackage.java +++ b/src/main/java/com/comphenix/protocol/utility/CachedPackage.java @@ -1,92 +1,90 @@ /** - * 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 + * 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.HashMap; import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.common.collect.Maps; /** * Represents a dynamic package and an arbitrary number of cached classes. - * + * * @author Kristian */ -class CachedPackage { - private final Map>> cache; +final class CachedPackage { + private final String packageName; private final ClassSource source; + private final Map>> cache; /** * Construct a new cached package. + * * @param packageName - the name of the current package. - * @param source - the class source. + * @param source - the class source. */ public CachedPackage(String packageName, ClassSource source) { - this.packageName = packageName; - this.cache = new ConcurrentHashMap<>(); this.source = source; + this.packageName = packageName; + this.cache = new HashMap<>(); + } + + /** + * Correctly combine a package name and the child class we're looking for. + * + * @param packageName - name of the package, or an empty string for the default package. + * @param className - the class name. + * @return We full class path. + */ + public static String combine(String packageName, String className) { + if (packageName == null || packageName.isEmpty()) { + return className; + } else { + return packageName + "." + className; + } } /** * Associate a given class with a class name. + * * @param className - class name. - * @param clazz - type of class. + * @param clazz - type of class. */ public void setPackageClass(String className, Class clazz) { if (clazz != null) { - cache.put(className, Optional.of(clazz)); + this.cache.put(className, Optional.of(clazz)); } else { - cache.remove(className); + this.cache.remove(className); } } /** * 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. */ public Optional> getPackageClass(final String className) { - Preconditions.checkNotNull(className, "className cannot be null!"); - - return cache.computeIfAbsent(className, x -> { + return this.cache.computeIfAbsent(className, x -> { try { - return Optional.ofNullable(source.loadClass(combine(packageName, className))); + return Optional.ofNullable(this.source.loadClass(combine(this.packageName, className))); } catch (ClassNotFoundException ex) { return Optional.empty(); } }); } - - /** - * Correctly combine a package name and the child class we're looking for. - * @param packageName - name of the package, or an empty string for the default package. - * @param className - the class name. - * @return We full class path. - */ - public static String combine(String packageName, String className) { - if (Strings.isNullOrEmpty(packageName)) - return className; - if (Strings.isNullOrEmpty(className)) - return packageName; - return packageName + "." + className; - } } diff --git a/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java b/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java index 9a9761cd..295c32fd 100644 --- a/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java +++ b/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java @@ -17,76 +17,38 @@ package com.comphenix.protocol.utility; -import java.lang.reflect.InvocationTargetException; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.wrappers.EnumWrappers; +import com.comphenix.protocol.wrappers.WrappedChatComponent; +import com.google.common.base.Strings; import java.util.ArrayList; import java.util.List; import java.util.UUID; - -import com.comphenix.protocol.wrappers.EnumWrappers; -import com.comphenix.protocol.wrappers.WrappedChatComponent; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.ProtocolManager; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.reflect.FieldAccessException; -import com.google.common.base.Strings; - /** * Utility methods for sending chat messages. - * + * * @author Kristian */ -public class ChatExtensions { - // Used to sent chat messages - private final ProtocolManager manager; +public final class ChatExtensions { private static final UUID SERVER_UUID = new UUID(0L, 0L); + // Used to sent chat messages + private final ProtocolManager manager; + public ChatExtensions(ProtocolManager manager) { this.manager = manager; } - - /** - * Send a message without invoking the packet listeners. - * @param receiver - the receiver. - * @param message - the message to send. - * @throws InvocationTargetException If we were unable to send the message. - */ - public void sendMessageSilently(CommandSender receiver, String message) throws InvocationTargetException { - if (receiver == null) - throw new IllegalArgumentException("receiver cannot be NULL."); - if (message == null) - throw new IllegalArgumentException("message cannot be NULL."); - - // Handle the player case by manually sending packets - if (receiver instanceof Player) { - sendMessageSilently((Player) receiver, message); - } else { - receiver.sendMessage(message); - } - } - - /** - * Send a message without invoking the packet listeners. - * @param player - the player to send it to. - * @param message - the message to send. - * @throws InvocationTargetException If we were unable to send the message. - */ - private void sendMessageSilently(Player player, String message) throws InvocationTargetException { - try { - for (PacketContainer packet : createChatPackets(message)) { - manager.sendServerPacket(player, packet, false); - } - } catch (FieldAccessException e) { - throw new InvocationTargetException(e); - } - } /** * Construct chat packet to send in order to display a given message. + * * @param message - the message to send. * @return The packets. */ @@ -110,45 +72,28 @@ public class ChatExtensions { return packets; } - /** - * Broadcast a message without invoking any packet listeners. - * @param message - message to send. - * @param permission - permission required to receieve the message. NULL to target everyone. - * @throws InvocationTargetException If we were unable to send the message. - */ - public void broadcastMessageSilently(String message, String permission) throws InvocationTargetException { - if (message == null) - throw new IllegalArgumentException("message cannot be NULL."); - - // Send this message to every online player - for (Player player : Bukkit.getOnlinePlayers()) { - if (permission == null || player.hasPermission(permission)) { - sendMessageSilently(player, message); - } - } - } - /** * Print a flower box around a given message. - * @param message - the message to print. - * @param marginChar - the character to use as margin. - * @param marginWidth - the width (in characters) of the left and right margin. + * + * @param message - the message to print. + * @param marginChar - the character to use as margin. + * @param marginWidth - the width (in characters) of the left and right margin. * @param marginHeight - the height (in characters) of the top and buttom margin. * @return Flowerboxed message */ public static String[] toFlowerBox(String[] message, String marginChar, int marginWidth, int marginHeight) { String[] output = new String[message.length + marginHeight * 2]; int width = getMaximumLength(message); - + // Margins String topButtomMargin = Strings.repeat(marginChar, width + marginWidth * 2); String leftRightMargin = Strings.repeat(marginChar, marginWidth); - + // Add left and right margin for (int i = 0; i < message.length; i++) { output[i + marginHeight] = leftRightMargin + Strings.padEnd(message[i], width, ' ') + leftRightMargin; } - + // Insert top and bottom margin for (int i = 0; i < marginHeight; i++) { output[i] = topButtomMargin; @@ -156,21 +101,76 @@ public class ChatExtensions { } return output; } - + /** * Retrieve the longest line lenght in a list of strings. + * * @param lines - the lines. * @return Longest line lenght. */ private static int getMaximumLength(String[] lines) { int current = 0; - + // Find the longest line for (String line : lines) { - if (current < line.length()) + if (current < line.length()) { current = line.length(); + } } return current; } + + /** + * Send a message without invoking the packet listeners. + * + * @param receiver - the receiver. + * @param message - the message to send. + */ + public void sendMessageSilently(CommandSender receiver, String message) { + if (receiver == null) { + throw new IllegalArgumentException("receiver cannot be NULL."); + } + if (message == null) { + throw new IllegalArgumentException("message cannot be NULL."); + } + + // Handle the player case by manually sending packets + if (receiver instanceof Player) { + this.sendMessageSilently((Player) receiver, message); + } else { + receiver.sendMessage(message); + } + } + + /** + * Send a message without invoking the packet listeners. + * + * @param player - the player to send it to. + * @param message - the message to send. + */ + private void sendMessageSilently(Player player, String message) { + for (PacketContainer packet : createChatPackets(message)) { + this.manager.sendServerPacket(player, packet, false); + } + } + + /** + * Broadcast a message without invoking any packet listeners. + * + * @param message - message to send. + * @param permission - permission required to receieve the message. NULL to target everyone. + */ + public void broadcastMessageSilently(String message, String permission) { + if (message == null) { + throw new IllegalArgumentException("message cannot be NULL."); + } + + // Send this message to every online player + for (Player player : Bukkit.getOnlinePlayers()) { + if (permission == null || player.hasPermission(permission)) { + this.sendMessageSilently(player, message); + } + } + } } diff --git a/src/main/java/com/comphenix/protocol/utility/ClassSource.java b/src/main/java/com/comphenix/protocol/utility/ClassSource.java index 658ff489..1ddc9956 100644 --- a/src/main/java/com/comphenix/protocol/utility/ClassSource.java +++ b/src/main/java/com/comphenix/protocol/utility/ClassSource.java @@ -5,145 +5,122 @@ import java.util.Map; /** * Represents an abstract class loader that can only retrieve classes by their canonical name. + * * @author Kristian */ -public abstract class ClassSource { +@FunctionalInterface +public interface ClassSource { + /** * Construct a class source from the default class loader. + * * @return A class source. */ - public static ClassSource fromClassLoader() { + static ClassSource fromClassLoader() { return fromClassLoader(ClassSource.class.getClassLoader()); } /** * Construct a class source from the default class loader and package. + * * @param packageName - the package that is prepended to every lookup. * @return A package source. */ - public static ClassSource fromPackage(String packageName) { + static ClassSource fromPackage(String packageName) { return fromClassLoader().usingPackage(packageName); } /** * Construct a class source from the given class loader. + * * @param loader - the class loader. * @return The corresponding class source. */ - public static ClassSource fromClassLoader(final ClassLoader loader) { - return new ClassSource() { - @Override - public Class loadClass(String canonicalName) throws ClassNotFoundException { - return loader.loadClass(canonicalName); - } - }; + static ClassSource fromClassLoader(final ClassLoader loader) { + return loader::loadClass; } /** - * Construct a class source from a mapping of canonical names and the corresponding classes. - * If the map is null, it will be interpreted as an empty map. If the map does not contain a Class with the specified name, or that string maps to NULL explicitly, a {@link ClassNotFoundException} will be thrown. + * Construct a class source from a mapping of canonical names and the corresponding classes. If the map is null, it + * will be interpreted as an empty map. If the map does not contain a Class with the specified name, or that string + * maps to NULL explicitly, a {@link ClassNotFoundException} will be thrown. + * * @param map - map of class names and classes. * @return The class source. */ - public static ClassSource fromMap(final Map> map) { - return new ClassSource() { - @Override - public Class loadClass(String canonicalName) throws ClassNotFoundException { - Class loaded = map == null ? null : map.get(canonicalName); - if(loaded == null){ - // Throw the appropriate exception if we can't load the class - throw new ClassNotFoundException("The specified class could not be found by this ClassLoader."); - } - return loaded; + static ClassSource fromMap(final Map> map) { + return canonicalName -> { + Class loaded = map == null ? null : map.get(canonicalName); + if (loaded == null) { + // Throw the appropriate exception if we can't load the class + throw new ClassNotFoundException("The specified class could not be found by this ClassLoader."); } + + return loaded; }; } - - /** - * @return A ClassLoader which will never successfully load a class. - */ - public static ClassSource empty(){ - return fromMap(Collections.>emptyMap()); - } /** - * Retrieve a class source that will attempt lookups in each of the given sources in the order they are in the array, and return the first value that is found. - * If the sources array is null or composed of any null elements, an exception will be thrown. - * @param sources - the class sources. - * @return A new class source. + * @return A ClassLoader which will never successfully load a class. */ - public static ClassSource attemptLoadFrom(final ClassSource... sources) { - if(sources.length == 0){ // Throws NPE if sources is null, which is what we want - return ClassSource.empty(); + static ClassSource empty() { + return fromMap(Collections.emptyMap()); + } + + /** + * Append to canonical names together. + * + * @param a - the name to the left. + * @param b - the name to the right. + * @return The full canonical name, with a dot seperator. + */ + static String append(String a, String b) { + boolean left = a.endsWith("."); + boolean right = b.endsWith("."); + + // Only add a dot if necessary + if (left && right) { + return a.substring(0, a.length() - 1) + b; + } else if (left != right) { + return a + b; + } else { + return a + "." + b; } - - ClassSource source = null; - for(int i = 0; i < sources.length; i++){ - if(sources[i] == null){ - throw new IllegalArgumentException("Null values are not permitted as ClassSources."); - } - - source = source == null ? sources[i] : source.retry(sources[i]); - } - return source; } /** * Retrieve a class source that will retry failed lookups in the given source. + * * @param other - the other class source. * @return A new class source. */ - public ClassSource retry(final ClassSource other) { - return new ClassSource() { - @Override - public Class loadClass(String canonicalName) throws ClassNotFoundException { - try { - return ClassSource.this.loadClass(canonicalName); - } catch (ClassNotFoundException e) { - return other.loadClass(canonicalName); - } + default ClassSource retry(final ClassSource other) { + return canonicalName -> { + try { + return ClassSource.this.loadClass(canonicalName); + } catch (ClassNotFoundException e) { + return other.loadClass(canonicalName); } }; } /** * Retrieve a class source that prepends a specific package name to every lookup. + * * @param packageName - the package name to prepend. * @return The class source. */ - public ClassSource usingPackage(final String packageName) { - return new ClassSource() { - @Override - public Class loadClass(String canonicalName) throws ClassNotFoundException { - return ClassSource.this.loadClass(append(packageName, canonicalName)); - } - }; - } - - /** - * Append to canonical names together. - * @param a - the name to the left. - * @param b - the name to the right. - * @return The full canonical name, with a dot seperator. - */ - protected static String append(String a, String b) { - boolean left = a.endsWith("."); - boolean right = b.endsWith("."); - - // Only add a dot if necessary - if (left && right) - return a.substring(0, a.length() - 1) + b; - else if (left != right) - return a + b; - else - return a + "." + b; + default ClassSource usingPackage(final String packageName) { + return canonicalName -> this.loadClass(append(packageName, canonicalName)); } /** * Retrieve a class by name. + * * @param canonicalName - the full canonical name of the class. - * @return The corresponding class. If the class is not found, NULL should not be returned, instead a {@code ClassNotFoundException} exception should be thrown. + * @return The corresponding class. If the class is not found, NULL should not be returned, instead a {@code + * ClassNotFoundException} exception should be thrown. * @throws ClassNotFoundException If the class could not be found. */ - public abstract Class loadClass(String canonicalName) throws ClassNotFoundException; + Class loadClass(String canonicalName) throws ClassNotFoundException; } diff --git a/src/main/java/com/comphenix/protocol/utility/Closer.java b/src/main/java/com/comphenix/protocol/utility/Closer.java index 8cc32b07..1ca2544f 100644 --- a/src/main/java/com/comphenix/protocol/utility/Closer.java +++ b/src/main/java/com/comphenix/protocol/utility/Closer.java @@ -1,18 +1,16 @@ /** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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 + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2015 dmulloy2 + *

    + * 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; @@ -24,6 +22,8 @@ import java.util.List; * @author dmulloy2 */ +// TODO Switch to AutoCloseable w/ Java 7 +@Deprecated public class Closer implements AutoCloseable { private final List list; @@ -35,6 +35,13 @@ public class Closer implements AutoCloseable { return new Closer(); } + public static void closeQuietly(Closeable close) { + try { + close.close(); + } catch (Throwable ex) { + } + } + public C register(C close) { list.add(close); return close; @@ -47,10 +54,4 @@ public class Closer implements AutoCloseable { } } - public static void closeQuietly(Closeable close) { - try { - close.close(); - } catch (Throwable ex) { } - } - } diff --git a/src/main/java/com/comphenix/protocol/utility/Constants.java b/src/main/java/com/comphenix/protocol/utility/Constants.java deleted file mode 100644 index 3d1d520e..00000000 --- a/src/main/java/com/comphenix/protocol/utility/Constants.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2015 dmulloy2 - * - * 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; - -/** - * @author dmulloy2 - */ - -public final class Constants { - public static final String PACKAGE_VERSION = "v1_19_R1"; - public static final String NMS = "net.minecraft"; - public static final String OBC = "org.bukkit.craftbukkit." + PACKAGE_VERSION; - public static final MinecraftVersion CURRENT_VERSION = MinecraftVersion.WILD_UPDATE; - - public static void init() { - MinecraftReflection.setMinecraftPackage(NMS, OBC); - MinecraftVersion.setCurrentVersion(CURRENT_VERSION); - } -} diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftFields.java b/src/main/java/com/comphenix/protocol/utility/MinecraftFields.java index 625e46ef..1271b269 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftFields.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftFields.java @@ -1,17 +1,17 @@ package com.comphenix.protocol.utility; -import org.bukkit.entity.Player; - import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import com.google.common.base.Preconditions; +import org.bukkit.entity.Player; /** * Retrieve the content of well-known fields in Minecraft. + * * @author Kristian */ -public class MinecraftFields { +public final class MinecraftFields { + // Cached accessors private static volatile FieldAccessor CONNECTION_ACCESSOR; private static volatile FieldAccessor NETWORK_ACCESSOR; @@ -20,50 +20,53 @@ public class MinecraftFields { private MinecraftFields() { // Not constructable } - + /** * Retrieve the network manager associated with a particular player. + * * @param player - the player. * @return The network manager, or NULL if no network manager has been associated yet. */ public static Object getNetworkManager(Player player) { Object nmsPlayer = BukkitUnwrapper.getInstance().unwrapItem(player); - + if (NETWORK_ACCESSOR == null) { Class networkClass = MinecraftReflection.getNetworkManagerClass(); Class connectionClass = MinecraftReflection.getPlayerConnectionClass(); NETWORK_ACCESSOR = Accessors.getFieldAccessor(connectionClass, networkClass, true); } + // Retrieve the network manager final Object playerConnection = getPlayerConnection(nmsPlayer); - - if (playerConnection != null) + if (playerConnection != null) { return NETWORK_ACCESSOR.get(playerConnection); + } + return null; } - + /** * Retrieve the PlayerConnection (or NetServerHandler) associated with a player. + * * @param player - the player. * @return The player connection. */ public static Object getPlayerConnection(Player player) { - Preconditions.checkNotNull(player, "player cannot be null!"); return getPlayerConnection(BukkitUnwrapper.getInstance().unwrapItem(player)); } /** * Retrieve the PlayerConnection (or NetServerHandler) associated with a player. + * * @param nmsPlayer - the NMS player. * @return The player connection. */ public static Object getPlayerConnection(Object nmsPlayer) { - Preconditions.checkNotNull(nmsPlayer, "nmsPlayer cannot be null!"); - if (CONNECTION_ACCESSOR == null) { Class connectionClass = MinecraftReflection.getPlayerConnectionClass(); CONNECTION_ACCESSOR = Accessors.getFieldAccessor(nmsPlayer.getClass(), connectionClass, true); } + return CONNECTION_ACCESSOR.get(nmsPlayer); } @@ -74,13 +77,12 @@ public class MinecraftFields { * @return The value of the EntityPlayer field in the PlayerConnection. */ public static Object getPlayerFromConnection(Object playerConnection) { - Preconditions.checkNotNull(playerConnection, "playerConnection cannot be null!"); - if (CONNECTION_ENTITY_ACCESSOR == null) { Class connectionClass = MinecraftReflection.getPlayerConnectionClass(); Class entityPlayerClass = MinecraftReflection.getEntityPlayerClass(); CONNECTION_ENTITY_ACCESSOR = Accessors.getFieldAccessor(connectionClass, entityPlayerClass, true); } + return CONNECTION_ENTITY_ACCESSOR.get(playerConnection); } } diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java b/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java index 7c8661a0..6a927b76 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java @@ -1,22 +1,20 @@ package com.comphenix.protocol.utility; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.List; -import java.util.Map; import java.util.concurrent.Callable; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; - -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelHandlerContext; - -import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.bind.annotation.Origin; @@ -26,158 +24,143 @@ import net.bytebuddy.matcher.ElementMatchers; /** * Static methods for accessing Minecraft methods. - * + * * @author Kristian */ -public class MinecraftMethods { - // For player connection - private volatile static Method sendPacketMethod; - - // For network manager - private volatile static Method networkManagerHandle; - private volatile static Method networkManagerPacketRead; - - // For packet - private volatile static Method packetReadByteBuf; - private volatile static Method packetWriteByteBuf; +public final class MinecraftMethods { + + // For player connection + private volatile static MethodAccessor sendPacketMethod; + private volatile static MethodAccessor disconnectMethod; + + // For network manager + private volatile static MethodAccessor networkManagerHandle; + private volatile static MethodAccessor networkManagerPacketRead; + + // For packet + private volatile static MethodAccessor packetReadByteBuf; + private volatile static MethodAccessor packetWriteByteBuf; + + // Decorated PacketSerializer to identify methods + private volatile static ConstructorAccessor decoratedDataSerializerAccessor; + + private MinecraftMethods() { + // sealed + } - private static Constructor proxyConstructor; - /** * Retrieve the send packet method in PlayerConnection/NetServerHandler. + * * @return The send packet method. */ - public static Method getSendPacketMethod() { + public static MethodAccessor getSendPacketMethod() { if (sendPacketMethod == null) { - Class serverHandlerClass = MinecraftReflection.getPlayerConnectionClass(); + FuzzyReflection serverHandlerClass = FuzzyReflection.fromClass(MinecraftReflection.getPlayerConnectionClass()); try { - sendPacketMethod = FuzzyReflection - .fromClass(serverHandlerClass) - .getMethod(FuzzyMethodContract.newBuilder() - .nameRegex("sendPacket.*") - .returnTypeVoid() - .parameterCount(1) - .build()); + sendPacketMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder() + .parameterCount(1) + .returnTypeVoid() + .parameterExactType(MinecraftReflection.getPacketClass(), 0) + .build())); } catch (IllegalArgumentException e) { - // We can't use the method below on Netty - if (MinecraftReflection.isUsingNetty()) { - sendPacketMethod = FuzzyReflection.fromClass(serverHandlerClass). - getMethodByParameters("sendPacket", MinecraftReflection.getPacketClass()); - return sendPacketMethod; - } - - Map netServer = getMethodList( - serverHandlerClass, MinecraftReflection.getPacketClass()); - Map netHandler = getMethodList( - MinecraftReflection.getNetHandlerClass(), MinecraftReflection.getPacketClass()); - - // Remove every method in net handler from net server - for (String methodName : netHandler.keySet()) { - netServer.remove(methodName); - } - - // The remainder is the send packet method - if (netServer.size() == 1) { - Method[] methods = netServer.values().toArray(new Method[0]); - sendPacketMethod = methods[0]; - } else { - throw new IllegalArgumentException("Unable to find the sendPacket method in NetServerHandler/PlayerConnection."); - } + sendPacketMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder() + .nameRegex("sendPacket.*") + .returnTypeVoid() + .parameterCount(1) + .build())); } } + return sendPacketMethod; } - + /** * Retrieve the disconnect method for a given player connection. + * * @param playerConnection - the player connection. * @return The */ - public static Method getDisconnectMethod(Class playerConnection) { - try { - return FuzzyReflection.fromClass(playerConnection).getMethodByName("disconnect.*"); - } catch (IllegalArgumentException e) { - // Just assume it's the first String method - return FuzzyReflection.fromObject(playerConnection).getMethodByParameters("disconnect", String.class); + public static MethodAccessor getDisconnectMethod(Class playerConnection) { + if (disconnectMethod == null) { + FuzzyReflection playerConnectionClass = FuzzyReflection.fromClass(playerConnection); + try { + disconnectMethod = Accessors.getMethodAccessor(playerConnectionClass.getMethod(FuzzyMethodContract.newBuilder() + .returnTypeVoid() + .nameRegex("disconnect.*") + .parameterCount(1) + .parameterExactType(String.class, 0) + .build())); + } catch (IllegalArgumentException e) { + // Just assume it's the first String method + Method disconnect = playerConnectionClass.getMethodByParameters("disconnect", String.class); + disconnectMethod = Accessors.getMethodAccessor(disconnect); + } } + + return disconnectMethod; } - + /** * Retrieve the handle/send packet method of network manager. - *

    - * This only exists in version 1.7.2 and above. + * * @return The handle method. */ - public static Method getNetworkManagerHandleMethod() { + public static MethodAccessor getNetworkManagerHandleMethod() { if (networkManagerHandle == null) { - networkManagerHandle = FuzzyReflection + Method handleMethod = FuzzyReflection .fromClass(MinecraftReflection.getNetworkManagerClass(), true) .getMethod(FuzzyMethodContract.newBuilder() .banModifier(Modifier.STATIC) .returnTypeVoid() .parameterCount(1) - .parameterExactType(MinecraftReflection.getPacketClass()) + .parameterExactType(MinecraftReflection.getPacketClass(), 0) .build()); - networkManagerHandle.setAccessible(true); + networkManagerHandle = Accessors.getMethodAccessor(handleMethod); } return networkManagerHandle; } - + /** * Retrieve the packetRead(ChannelHandlerContext, Packet) method of NetworkManager. - *

    - * This only exists in version 1.7.2 and above. + * * @return The packetRead method. */ - public static Method getNetworkManagerReadPacketMethod() { + public static MethodAccessor getNetworkManagerReadPacketMethod() { if (networkManagerPacketRead == null) { - networkManagerPacketRead = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass(), true). - getMethodByParameters("packetRead", ChannelHandlerContext.class, MinecraftReflection.getPacketClass()); - networkManagerPacketRead.setAccessible(true); + Method messageReceived = FuzzyReflection + .fromClass(MinecraftReflection.getNetworkManagerClass(), true) + .getMethodByParameters("packetRead", ChannelHandlerContext.class, MinecraftReflection.getPacketClass()); + networkManagerPacketRead = Accessors.getMethodAccessor(messageReceived); } + return networkManagerPacketRead; } - /** - * Retrieve a method mapped list of every method with the given signature. - * @param source - class source. - * @param params - parameters. - * @return Method mapped list. - */ - private static Map getMethodList(Class source, Class... params) { - FuzzyReflection reflect = FuzzyReflection.fromClass(source, true); - - return reflect.getMappedMethods( - reflect.getMethodListByParameters(Void.TYPE, params) - ); - } - /** * Retrieve the Packet.read(PacketDataSerializer) method. - *

    - * This only exists in version 1.7.2 and above. + * * @return The packet read method. */ - public static Method getPacketReadByteBufMethod() { + public static MethodAccessor getPacketReadByteBufMethod() { initializePacket(); return packetReadByteBuf; } - + /** * Retrieve the Packet.write(PacketDataSerializer) method. *

    * This only exists in version 1.7.2 and above. + * * @return The packet write method. */ - public static Method getPacketWriteByteBufMethod() { + public static MethodAccessor getPacketWriteByteBufMethod() { initializePacket(); return packetWriteByteBuf; } - private static Constructor setupProxyConstructor() - { + private static Constructor setupProxyConstructor() { try { return ByteBuddyFactory.getInstance() .createSubclass(MinecraftReflection.getPacketDataSerializerClass()) @@ -209,71 +192,71 @@ public class MinecraftMethods { * Initialize the two read() and write() methods. */ private static void initializePacket() { - // Initialize the methods if (packetReadByteBuf == null || packetWriteByteBuf == null) { - if (proxyConstructor == null) - proxyConstructor = setupProxyConstructor(); - - final Object javaProxy; - try { - javaProxy = proxyConstructor.newInstance(Unpooled.buffer()); - } catch (IllegalAccessException e) { - throw new RuntimeException("Cannot access reflection.", e); - } catch (InstantiationException e) { - throw new RuntimeException("Cannot instantiate object.", e); - } catch (InvocationTargetException e) { - throw new RuntimeException("Error in invocation.", e); + // setups a decorated PacketDataSerializer which we can use to identity read/write methods in the packet class + if (decoratedDataSerializerAccessor == null) { + decoratedDataSerializerAccessor = Accessors.getConstructorAccessor(setupProxyConstructor()); } - final Object lookPacket = new PacketContainer(PacketType.Play.Client.CLOSE_WINDOW).getHandle(); - final List candidates = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()) - .getMethodListByParameters(Void.TYPE, new Class[] { MinecraftReflection.getPacketDataSerializerClass() }); + // constructs a new decorated serializer + Object decoratedSerializer = decoratedDataSerializerAccessor.invoke(Unpooled.EMPTY_BUFFER); - // Look through all the methods - for (Method method : candidates) { + // find all methods which might be the read or write methods + List candidates = FuzzyReflection + .fromClass(MinecraftReflection.getPacketClass()) + .getMethodListByParameters(Void.TYPE, MinecraftReflection.getPacketDataSerializerClass()); + // a constructed, empty packet on which we can call the methods + Object dummyPacket = new PacketContainer(PacketType.Play.Client.CLOSE_WINDOW).getHandle(); + + for (Method candidate : candidates) { + // invoke the method and see if it's a write or read method try { - method.invoke(lookPacket, javaProxy); - } catch (InvocationTargetException e) { - if (e.getCause() instanceof ReadMethodException) { - // Must be the reader - packetReadByteBuf = method; - } else if (e.getCause() instanceof WriteMethodException) { - packetWriteByteBuf = method; - } else { - // throw new RuntimeException("Inner exception.", e); + candidate.invoke(dummyPacket, decoratedSerializer); + } catch (InvocationTargetException exception) { + // check for the cause of the exception + if (exception.getCause() instanceof ReadMethodException) { + // must the read method + packetReadByteBuf = Accessors.getMethodAccessor(candidate); + } else if (exception.getCause() instanceof WriteMethodException) { + // must be the write method + packetWriteByteBuf = Accessors.getMethodAccessor(candidate); } - } catch (Exception e) { - throw new RuntimeException("Generic reflection error.", e); + } catch (IllegalAccessException exception) { + throw new RuntimeException("Unable to invoke " + candidate, exception); } } -// if (packetReadByteBuf == null) -// throw new IllegalStateException("Unable to find Packet.read(PacketDataSerializer)"); - if (packetWriteByteBuf == null) + // write must be there, read is gone since 1.18 (handled via constructor) + if (packetWriteByteBuf == null) { throw new IllegalStateException("Unable to find Packet.write(PacketDataSerializer)"); + } } } - + /** * An internal exception used to detect read methods. + * * @author Kristian */ private static class ReadMethodException extends RuntimeException { + private static final long serialVersionUID = 1L; public ReadMethodException() { super("A read method was executed."); } } - + /** * An internal exception used to detect write methods. + * * @author Kristian */ private static class WriteMethodException extends RuntimeException { + private static final long serialVersionUID = 1L; - + public WriteMethodException() { super("A write method was executed."); } diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftProtocolVersion.java b/src/main/java/com/comphenix/protocol/utility/MinecraftProtocolVersion.java index bfe881a5..b38fa0b3 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftProtocolVersion.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftProtocolVersion.java @@ -6,14 +6,16 @@ import java.util.TreeMap; /** * A lookup of the associated protocol version of a given Minecraft server. + * * @author Kristian */ -public class MinecraftProtocolVersion { - private static final NavigableMap lookup = createLookup(); - +public final class MinecraftProtocolVersion { + + private static final NavigableMap LOOKUP = createLookup(); + private static NavigableMap createLookup() { TreeMap map = new TreeMap<>(); - + // Source: http://wiki.vg/Protocol_version_numbers // Doesn't include pre-releases map.put(new MinecraftVersion(1, 0, 0), 22); @@ -31,7 +33,7 @@ public class MinecraftProtocolVersion { map.put(new MinecraftVersion(1, 6, 1), 73); map.put(new MinecraftVersion(1, 6, 2), 74); map.put(new MinecraftVersion(1, 6, 4), 78); - + // After Netty map.put(new MinecraftVersion(1, 7, 1), 4); map.put(new MinecraftVersion(1, 7, 6), 5); @@ -86,19 +88,21 @@ public class MinecraftProtocolVersion { /** * Retrieve the version of the Minecraft protocol for the current version of Minecraft. + * * @return The version number. */ public static int getCurrentVersion() { return getVersion(MinecraftVersion.getCurrentVersion()); } - + /** * Retrieve the version of the Minecraft protocol for this version of Minecraft. + * * @param version - the version. * @return The version number. */ public static int getVersion(MinecraftVersion version) { - Entry result = lookup.floorEntry(version); + Entry result = LOOKUP.floorEntry(version); return result != null ? result.getValue() : Integer.MIN_VALUE; } } diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index badfb810..4fa61499 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -17,262 +17,168 @@ package com.comphenix.protocol.utility; -import java.io.DataInputStream; -import java.io.DataOutput; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLogger; +import com.comphenix.protocol.injector.BukkitUnwrapper; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; +import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.wrappers.EnumWrappers; import java.lang.reflect.Array; import java.lang.reflect.Constructor; -import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.net.InetAddress; -import java.util.Collection; -import java.util.HashSet; +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.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; - -import javax.annotation.Nonnull; - import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.Server; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.ProtocolLogger; -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.injector.BukkitUnwrapper; -import com.comphenix.protocol.injector.packet.PacketRegistry; -import com.comphenix.protocol.reflect.ClassAnalyser; -import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import com.comphenix.protocol.reflect.accessors.MethodAccessor; -import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; -import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract; -import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; -import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException; -import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException.Reason; -import com.comphenix.protocol.wrappers.EnumWrappers; -import com.comphenix.protocol.wrappers.nbt.NbtFactory; -import com.comphenix.protocol.wrappers.nbt.NbtType; -import com.google.common.base.Joiner; - /** * Methods and constants specifically used in conjuction with reflecting Minecraft object. * * @author Kristian */ -public class MinecraftReflection { - public static final ReportType REPORT_CANNOT_FIND_MCPC_REMAPPER = new ReportType("Cannot find MCPC/Cauldron remapper."); - public static final ReportType REPORT_CANNOT_LOAD_CPC_REMAPPER = new ReportType("Unable to load MCPC/Cauldron remapper."); - public static final ReportType REPORT_NON_CRAFTBUKKIT_LIBRARY_PACKAGE = new ReportType("Cannot find standard Minecraft library location. Assuming MCPC/Cauldron."); +public final class MinecraftReflection { + + private static final ClassSource CLASS_SOURCE = ClassSource.fromClassLoader(); /** * Regular expression that matches a canonical Java class. */ - private static final String CANONICAL_REGEX = "(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)+\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; + private static final String CANONICAL_REGEX = "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; + private static final String MINECRAFT_CLASS_NAME_REGEX = "net\\.minecraft\\." + CANONICAL_REGEX; /** - * Regular expression that matches a Minecraft object. - *

    - * Replaced by the method {@link #getMinecraftObjectRegex()}. - */ - @Deprecated - public static final String MINECRAFT_OBJECT = "net\\.minecraft\\." + CANONICAL_REGEX; - - /** - * Regular expression computed dynamically. - */ - private static String DYNAMIC_PACKAGE_MATCHER = null; - - /** - * The Entity package in Forge 1.5.2 - */ - private static final String FORGE_ENTITY_PACKAGE = "net.minecraft.entity"; - - /** - * The package name of all the classes that belongs to the native code in Minecraft. - */ - private static String MINECRAFT_PREFIX_PACKAGE = "net.minecraft.server"; - - /** - * Represents a regular expression that will match the version string in a package: - * org.bukkit.craftbukkit.v1_6_R2 -> v1_6_R2 + * Represents a regular expression that will match the version string in a package: org.bukkit.craftbukkit.v1_6_R2 -> + * v1_6_R2 */ private static final Pattern PACKAGE_VERSION_MATCHER = Pattern.compile(".*\\.(v\\d+_\\d+_\\w*\\d+)"); - private static String MINECRAFT_FULL_PACKAGE = null; - private static String CRAFTBUKKIT_PACKAGE = null; + // Cache of getBukkitEntity + private static final Map, MethodAccessor> BUKKIT_ENTITY_CACHE = new HashMap<>(); // Package private for the purpose of unit testing static CachedPackage minecraftPackage; static CachedPackage craftbukkitPackage; static CachedPackage libraryPackage; - // Matches classes - private static AbstractFuzzyMatcher> fuzzyMatcher; + /** + * Regular expression computed dynamically. + */ + private static String DYNAMIC_PACKAGE_MATCHER = null; + /** + * The package name of all the classes that belongs to the native code in Minecraft. + */ + private static String MINECRAFT_PREFIX_PACKAGE = "net.minecraft.server"; + private static String MINECRAFT_FULL_PACKAGE = null; + private static String CRAFTBUKKIT_PACKAGE = null; + // fuzzy matcher for minecraft class objects + private static AbstractFuzzyMatcher> fuzzyMatcher; // The NMS version private static String packageVersion; - - // Item stacks - /* private static Method craftNMSMethod; - private static Method craftBukkitNMS; - private static Method craftBukkitOBC; - private static boolean craftItemStackFailed; - - private static Constructor craftNMSConstructor; - private static Constructor craftBukkitConstructor; */ - // net.minecraft.server private static Class itemStackArrayClass; - - // Cache of getBukkitEntity - private static final ConcurrentMap, MethodAccessor> getBukkitEntityCache = new ConcurrentHashMap<>(); - - // The current class source - private static ClassSource classSource; - - /** - * Whether or not we're currently initializing the reflection handler. - */ - private static boolean initializing; - - // Whether or not we are using netty - private static Boolean cachedNetty; + // Whether we are using netty private static Boolean cachedWatcherObject; + // ---- ItemStack conversions + private static Object itemStackAir = null; + private static Boolean nullEnforced = null; + + private static MethodAccessor asNMSCopy = null; + private static MethodAccessor asCraftMirror = null; + private static MethodAccessor isEmpty = null; + private MinecraftReflection() { // No need to make this constructable. } /** * Retrieve a regular expression that can match Minecraft package objects. + * * @return Minecraft package matcher. */ public static String getMinecraftObjectRegex() { - if (DYNAMIC_PACKAGE_MATCHER == null) + if (DYNAMIC_PACKAGE_MATCHER == null) { getMinecraftPackage(); + } return DYNAMIC_PACKAGE_MATCHER; } /** * Retrieve a abstract fuzzy class matcher for Minecraft objects. + * * @return A matcher for Minecraft objects. */ public static AbstractFuzzyMatcher> getMinecraftObjectMatcher() { - if (fuzzyMatcher == null) - fuzzyMatcher = FuzzyMatchers.matchRegex(getMinecraftObjectRegex(), 50); + if (fuzzyMatcher == null) { + fuzzyMatcher = FuzzyMatchers.matchRegex(getMinecraftObjectRegex()); + } return fuzzyMatcher; } /** * Retrieve the name of the Minecraft server package. + * * @return Full canonical name of the Minecraft server package. */ public static String getMinecraftPackage() { // Speed things up - if (MINECRAFT_FULL_PACKAGE != null) + if (MINECRAFT_FULL_PACKAGE != null) { return MINECRAFT_FULL_PACKAGE; - if (initializing) - throw new IllegalStateException("Already initializing minecraft package!"); - initializing = true; + } - Server craftServer = Bukkit.getServer(); + try { + // get the bukkit version we're running on + Server craftServer = Bukkit.getServer(); + CRAFTBUKKIT_PACKAGE = craftServer.getClass().getPackage().getName(); - // This server should have a "getHandle" method that we can use - if (craftServer != null) { - try { - // The return type will tell us the full package, regardless of formating - Class craftClass = craftServer.getClass(); - CRAFTBUKKIT_PACKAGE = getPackage(craftClass.getCanonicalName()); + // Parse the package version + Matcher packageMatcher = PACKAGE_VERSION_MATCHER.matcher(CRAFTBUKKIT_PACKAGE); + if (packageMatcher.matches()) { + packageVersion = packageMatcher.group(1); + } else { + MinecraftVersion version = new MinecraftVersion(craftServer); - // Parse the package version - Matcher packageMatcher = PACKAGE_VERSION_MATCHER.matcher(CRAFTBUKKIT_PACKAGE); - if (packageMatcher.matches()) { - packageVersion = packageMatcher.group(1); - } else { - MinecraftVersion version = new MinecraftVersion(craftServer); - - // See if we need a package version - if (MinecraftVersion.SCARY_UPDATE.compareTo(version) <= 0) { - // Just assume R1 - it's probably fine - packageVersion = "v" + version.getMajor() + "_" + version.getMinor() + "_R1"; - ProtocolLogger.log(Level.WARNING, "Assuming package version: " + packageVersion); - } - } - - // Libigot patch - handleLibigot(); - - // Next, do the same for CraftEntity.getHandle() in order to get the correct Minecraft package - Class craftEntity = getCraftEntityClass(); - Method getHandle = craftEntity.getMethod("getHandle"); - - if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { - // total rework of the NMS structure in 1.17 (at least there's no versioning) - MINECRAFT_FULL_PACKAGE = MINECRAFT_PREFIX_PACKAGE = "net.minecraft"; - setDynamicPackageMatcher(MINECRAFT_OBJECT); - } else { - MINECRAFT_FULL_PACKAGE = getPackage(getHandle.getReturnType().getCanonicalName()); - - // Pretty important invariantt - if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) { - // See if we got the Forge entity package - if (MINECRAFT_FULL_PACKAGE.equals(FORGE_ENTITY_PACKAGE)) { - // USe the standard NMS versioned package - MINECRAFT_FULL_PACKAGE = CachedPackage.combine(MINECRAFT_PREFIX_PACKAGE, packageVersion); - } else { - // Assume they're the same instead - MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE; - } - - // The package is usualy flat, so go with that assumption - String matcher = - (MINECRAFT_PREFIX_PACKAGE.length() > 0 ? Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + CANONICAL_REGEX; - - // We'll still accept the default location, however - setDynamicPackageMatcher("(" + matcher + ")|(" + MINECRAFT_OBJECT + ")"); - - } else { - // Use the standard matcher - setDynamicPackageMatcher(MINECRAFT_OBJECT); - } - } - - return MINECRAFT_FULL_PACKAGE; - - } catch (SecurityException e) { - throw new RuntimeException("Security violation. Cannot get handle method.", e); - } catch (NoSuchMethodException e) { - throw new IllegalStateException("Cannot find getHandle() method on server. Is this a modified CraftBukkit version?", e); - } finally { - initializing = false; + // Just assume R1 - it's probably fine (warn anyway) + packageVersion = "v" + version.getMajor() + "_" + version.getMinor() + "_R1"; + ProtocolLogger.log(Level.SEVERE, "Assuming package version: " + packageVersion); } - } else { - initializing = false; - throw new IllegalStateException("Could not find Bukkit. Is it running?"); + if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { + // total rework of the NMS structure in 1.17 (at least there's no versioning) + MINECRAFT_FULL_PACKAGE = MINECRAFT_PREFIX_PACKAGE = "net.minecraft"; + setDynamicPackageMatcher(MINECRAFT_CLASS_NAME_REGEX); + } else { + // extract the server version from the return type of "getHandle" in CraftEntity + Method getHandle = getCraftEntityClass().getMethod("getHandle"); + MINECRAFT_FULL_PACKAGE = getHandle.getReturnType().getPackage().getName(); + + setDynamicPackageMatcher(MINECRAFT_CLASS_NAME_REGEX); + } + + return MINECRAFT_FULL_PACKAGE; + } catch (NoSuchMethodException exception) { + throw new IllegalStateException("Cannot find getHandle() in CraftEntity", exception); } } /** * Retrieve the package version of the underlying CraftBukkit server. - * @return The package version, or NULL if not applicable (before 1.4.6). + * + * @return The craftbukkit package version. */ public static String getPackageVersion() { getMinecraftPackage(); @@ -281,35 +187,22 @@ public class MinecraftReflection { /** * Update the dynamic package matcher. + * * @param regex - the Minecraft package regex. */ private static void setDynamicPackageMatcher(String regex) { DYNAMIC_PACKAGE_MATCHER = regex; - // Ensure that the matcher is regenerated fuzzyMatcher = null; } - // Patch for Libigot - private static void handleLibigot() { - try { - getCraftEntityClass(); - } catch (RuntimeException e) { - // Try reverting the package to the old format - craftbukkitPackage = null; - CRAFTBUKKIT_PACKAGE = "org.bukkit.craftbukkit"; - - // This might fail too - getCraftEntityClass(); - } - } - /** * Used during debugging and testing. - * @param minecraftPackage - the current Minecraft package. + * + * @param minecraftPackage - the current Minecraft package. * @param craftBukkitPackage - the current CraftBukkit package. */ - public static void setMinecraftPackage(String minecraftPackage, String craftBukkitPackage) { + static void setMinecraftPackage(String minecraftPackage, String craftBukkitPackage) { MINECRAFT_FULL_PACKAGE = minecraftPackage; CRAFTBUKKIT_PACKAGE = craftBukkitPackage; @@ -319,58 +212,50 @@ public class MinecraftReflection { } // Standard matcher - setDynamicPackageMatcher(MINECRAFT_OBJECT); + setDynamicPackageMatcher(MINECRAFT_CLASS_NAME_REGEX); } /** * Retrieve the name of the root CraftBukkit package. + * * @return Full canonical name of the root CraftBukkit package. */ public static String getCraftBukkitPackage() { // Ensure it has been initialized - if (CRAFTBUKKIT_PACKAGE == null) + if (CRAFTBUKKIT_PACKAGE == null) { getMinecraftPackage(); + } + return CRAFTBUKKIT_PACKAGE; } - /** - * Retrieve the package name from a given canonical Java class name. - * @param fullName - full Java class name. - * @return The package name. - */ - private static String getPackage(String fullName) { - int index = fullName.lastIndexOf("."); - - if (index > 0) - return fullName.substring(0, index); - else - return ""; // Default package - } - /** * 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) + if (nmsObject == null) { return null; + } // We will have to do this dynamically, unfortunately try { Class clazz = nmsObject.getClass(); - MethodAccessor accessor = getBukkitEntityCache.get(clazz); + MethodAccessor accessor = BUKKIT_ENTITY_CACHE.get(clazz); if (accessor == null) { MethodAccessor created = Accessors.getMethodAccessor(clazz, "getBukkitEntity"); - accessor = getBukkitEntityCache.putIfAbsent(clazz, created); + accessor = BUKKIT_ENTITY_CACHE.putIfAbsent(clazz, created); // We won the race if (accessor == null) { accessor = created; } } + return accessor.invoke(nmsObject); } catch (Exception e) { throw new IllegalArgumentException("Cannot get Bukkit entity from " + nmsObject, e); @@ -379,12 +264,12 @@ public class MinecraftReflection { /** * Retrieve the Bukkit player from a given PlayerConnection. + * * @param playerConnection The PlayerConnection. * @return A bukkit player. * @throws RuntimeException If we were unable to retrieve the Bukkit player. */ - public static Player getBukkitPlayerFromConnection(Object playerConnection) - { + public static Player getBukkitPlayerFromConnection(Object playerConnection) { try { return (Player) getBukkitEntity(MinecraftFields.getPlayerFromConnection(playerConnection)); } catch (Exception e) { @@ -394,12 +279,14 @@ public class MinecraftReflection { /** * Determine if a given object can be found within the package net.minecraft.server. + * * @param obj - the object to test. * @return TRUE if it can, FALSE otherwise. */ - public static boolean isMinecraftObject(@Nonnull Object obj) { - if (obj == null) + public static boolean isMinecraftObject(Object obj) { + if (obj == null) { return false; + } // Doesn't matter if we don't check for the version here return obj.getClass().getName().startsWith(MINECRAFT_PREFIX_PACKAGE); @@ -407,36 +294,39 @@ public class MinecraftReflection { /** * Determine if the given class is found within the package net.minecraft.server, or any equivalent package. + * * @param clazz - the class to test. * @return TRUE if it can, FALSE otherwise. */ - public static boolean isMinecraftClass(@Nonnull Class clazz) { - if (clazz == null) + public static boolean isMinecraftClass(Class clazz) { + if (clazz == null) { throw new IllegalArgumentException("clazz cannot be NULL."); + } return getMinecraftObjectMatcher().isMatch(clazz, null); } /** * Determine if a given object is found in net.minecraft.server, and has the given name. - * @param obj - the object to test. + * + * @param obj - the object to test. * @param className - the class name to test. * @return TRUE if it can, FALSE otherwise. */ - public static boolean isMinecraftObject(@Nonnull Object obj, String className) { - if (obj == null) + public static boolean isMinecraftObject(Object obj, String className) { + if (obj == null) { return false; + } String javaName = obj.getClass().getName(); return javaName.startsWith(MINECRAFT_PREFIX_PACKAGE) && javaName.endsWith(className); - } + } /** - * Determine if a given Object is compatible with a given Class. That is, - * whether or not the Object is an instance of that Class or one of its - * subclasses. If either is null, false is returned. - * - * @param clazz Class to test for, may be null + * Determine if a given Object is compatible with a given Class. That is, whether or not the Object is an instance of + * that Class or one of its subclasses. If either is null, false is returned. + * + * @param clazz Class to test for, may be null * @param object the Object to test, may be null * @return True if it is, false if not * @see Class#isAssignableFrom(Class) @@ -460,17 +350,9 @@ public class MinecraftReflection { return clazz.isAssignableFrom(test); } - /** - * Determine if a given object is a ChunkPosition. - * @param obj - the object to test. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isChunkPosition(Object obj) { - return is(getChunkPositionClass(), obj); - } - /** * Determine if a given object is a BlockPosition. + * * @param obj - the object to test. * @return TRUE if it can, FALSE otherwise. */ @@ -480,6 +362,7 @@ public class MinecraftReflection { /** * Determine if the given object is an NMS ChunkCoordIntPar. + * * @param obj - the object. * @return TRUE if it can, FALSE otherwise. */ @@ -487,17 +370,9 @@ public class MinecraftReflection { return is(getChunkCoordIntPair(), obj); } - /** - * Determine if a given object is a ChunkCoordinate. - * @param obj - the object to test. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isChunkCoordinates(Object obj) { - return is(getChunkCoordinatesClass(), obj); - } - /** * Determine if the given object is actually a Minecraft packet. + * * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ @@ -505,17 +380,9 @@ public class MinecraftReflection { return is(getPacketClass(), obj); } - /** - * Determine if the given object is a NetLoginHandler (PendingConnection) - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isLoginHandler(Object obj) { - return is(getNetLoginHandlerClass(), obj); - } - /** * Determine if the given object is assignable to a NetServerHandler (PlayerConnection) + * * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ @@ -525,6 +392,7 @@ public class MinecraftReflection { /** * Determine if the given object is actually a Minecraft packet. + * * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ @@ -534,6 +402,7 @@ public class MinecraftReflection { /** * Determine if the given object is a NMS ItemStack. + * * @param value - the given object. * @return TRUE if it is, FALSE otherwise. */ @@ -543,6 +412,7 @@ public class MinecraftReflection { /** * Determine if the given object is a CraftPlayer class. + * * @param value - the given object. * @return TRUE if it is, FALSE otherwise. */ @@ -552,6 +422,7 @@ public class MinecraftReflection { /** * Determine if the given object is a Minecraft player entity. + * * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ @@ -559,17 +430,9 @@ public class MinecraftReflection { return is(getEntityPlayerClass(), obj); } - /** - * Determine if the given object is a watchable object. - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isWatchableObject(Object obj) { - return is(getWatchableObjectClass(), obj); - } - /** * Determine if the given object is a data watcher object. + * * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ @@ -579,6 +442,7 @@ public class MinecraftReflection { /** * Determine if the given object is an IntHashMap object. + * * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ @@ -588,6 +452,7 @@ public class MinecraftReflection { /** * Determine if the given object is a CraftItemStack instancey. + * * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ @@ -597,11 +462,12 @@ public class MinecraftReflection { /** * Retrieve the EntityPlayer (NMS) class. + * * @return The entity class. */ public static Class getEntityPlayerClass() { try { - return getMinecraftClass("server.level.EntityPlayer","EntityPlayer"); + return getMinecraftClass("server.level.EntityPlayer", "EntityPlayer"); } catch (RuntimeException e) { try { // Grab CraftPlayer's handle @@ -619,6 +485,7 @@ public class MinecraftReflection { /** * Retrieve the EntityHuman class. + * * @return The entity human class. */ public static Class getEntityHumanClass() { @@ -627,33 +494,17 @@ public class MinecraftReflection { } /** - * Retrieve the GameProfile class in 1.7.2 and later. - * + * Retrieve the GameProfile class. + * * @return The game profile class. - * @throws IllegalStateException If we are running 1.6.4 or earlier. */ public static Class getGameProfileClass() { - if (!isUsingNetty()) - throw new IllegalStateException("GameProfile does not exist in version 1.6.4 and earlier."); - - try { - return getClass("com.mojang.authlib.GameProfile"); - } catch (Throwable ex) { - try { - return getClass("net.minecraft.util.com.mojang.authlib.GameProfile"); - } catch (Throwable ex1) { - FuzzyReflection reflection = FuzzyReflection.fromClass(PacketType.Login.Server.SUCCESS.getPacketClass(), true); - FuzzyFieldContract contract = FuzzyFieldContract.newBuilder() - .banModifier(Modifier.STATIC) - .typeMatches(FuzzyMatchers.matchRegex("(.*)(GameProfile)", 1)) - .build(); - return reflection.getField(contract).getType(); - } - } + return getClass("com.mojang.authlib.GameProfile"); } /** * Retrieve the entity (NMS) class. + * * @return The entity class. */ public static Class getEntityClass() { @@ -665,7 +516,8 @@ public class MinecraftReflection { } /** - * Retrieve the CraftChatMessage in Minecraft 1.7.2. + * Retrieve the CraftChatMessage. + * * @return The CraftChatMessage class. */ public static Class getCraftChatMessage() { @@ -674,6 +526,7 @@ public class MinecraftReflection { /** * Retrieve the WorldServer (NMS) class. + * * @return The WorldServer class. */ public static Class getWorldServerClass() { @@ -686,6 +539,7 @@ public class MinecraftReflection { /** * Retrieve the World (NMS) class. + * * @return The world class. */ public static Class getNmsWorldClass() { @@ -698,109 +552,49 @@ public class MinecraftReflection { /** * Fallback on the return value of a named method in order to get a NMS class. - * @param nmsClass - the expected name of the Minecraft class. + * + * @param nmsClass - the expected name of the Minecraft class. * @param craftClass - a CraftBukkit class to look at. * @param methodName - the method we will use. * @return The return value of this method, which will be saved to the package cache. */ private static Class fallbackMethodReturn(String nmsClass, String craftClass, String methodName) { - Class result = FuzzyReflection.fromClass(getCraftBukkitClass(craftClass)). - getMethodByName(methodName).getReturnType(); - + Class result = FuzzyReflection.fromClass(getCraftBukkitClass(craftClass)) + .getMethodByName(methodName) + .getReturnType(); // Save the result return setMinecraftClass(nmsClass, result); } /** * Retrieve the packet class. + * * @return The packet class. */ public static Class getPacketClass() { - try { - return getMinecraftClass("network.protocol.Packet", "Packet"); - } catch (RuntimeException e) { - FuzzyClassContract paketContract = null; - - // What kind of class we're looking for (sanity check) - if (isUsingNetty()) { - paketContract = FuzzyClassContract.newBuilder(). - method(FuzzyMethodContract.newBuilder(). - parameterDerivedOf(getByteBufClass()). - returnTypeVoid()). - method(FuzzyMethodContract.newBuilder(). - parameterDerivedOf(getByteBufClass(), 0). - parameterExactType(byte[].class, 1). - returnTypeVoid()). - build(); - } else { - paketContract = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Map.class). - requireModifier(Modifier.STATIC)). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Set.class). - requireModifier(Modifier.STATIC)). - method(FuzzyMethodContract.newBuilder(). - parameterSuperOf(DataInputStream.class). - returnTypeVoid()). - build(); - } - - // Select a method with one Minecraft object parameter - Method selected = FuzzyReflection.fromClass(getPlayerConnectionClass()). - getMethod(FuzzyMethodContract.newBuilder(). - parameterMatches(paketContract, 0). - parameterCount(1). - build() - ); - - // Save and return - Class clazz = getTopmostClass(selected.getParameterTypes()[0]); - return setMinecraftClass("Packet", clazz); - } + return getMinecraftClass("network.protocol.Packet", "Packet"); } public static Class getByteBufClass() { - try { - return getClass("io.netty.buffer.ByteBuf"); - } catch (Throwable ex) { - return getClass("net.minecraft.util.io.netty.buffer.ByteBuf"); - } + return getClass("io.netty.buffer.ByteBuf"); } /** - * Retrieve the EnumProtocol class in 1.7.2. + * Retrieve the EnumProtocol class. + * * @return The Enum protocol class. */ public static Class getEnumProtocolClass() { return getMinecraftClass("network.EnumProtocol", "EnumProtocol"); - - /* try { - return getMinecraftClass("network.EnumProtocol", "EnumProtocol"); - } catch (RuntimeException e) { - Method protocolMethod = FuzzyReflection.fromClass(getNetworkManagerClass()).getMethod( - FuzzyMethodContract.newBuilder(). - parameterCount(1). - parameterDerivedOf(Enum.class, 0). - build() - ); - return setMinecraftClass("EnumProtocol", protocolMethod.getParameterTypes()[0]); - } */ } /** * Retrieve the IChatBaseComponent class. + * * @return The IChatBaseComponent. */ public static Class getIChatBaseComponentClass() { - try { - return getMinecraftClass("network.chat.IChatBaseComponent", "network.chat.IChatbaseComponent", "IChatBaseComponent"); - } catch (RuntimeException e) { - return setMinecraftClass("IChatBaseComponent", - Accessors.getMethodAccessor(getCraftChatMessage(), "fromString", String.class). - getMethod().getReturnType().getComponentType() - ); - } + return getMinecraftClass("network.chat.IChatBaseComponent", "network.chat.IChatbaseComponent", "IChatBaseComponent"); } public static Class getIChatBaseComponentArrayClass() { @@ -809,321 +603,125 @@ public class MinecraftReflection { /** * Retrieve the NMS chat component text class. + * * @return The chat component class. */ public static Class getChatComponentTextClass() { - try { - return getMinecraftClass("network.chat.ChatComponentText", "ChatComponentText"); - } catch (RuntimeException e) { - try { - Method getScoreboardDisplayName = FuzzyReflection.fromClass(getEntityClass()). - getMethodByParameters("getScoreboardDisplayName", getIChatBaseComponentClass(), new Class[0]); - Class baseClass = getIChatBaseComponentClass(); - - for (AsmMethod method : ClassAnalyser.getDefault().getMethodCalls(getScoreboardDisplayName)) { - Class owner = method.getOwnerClass(); - - if (isMinecraftClass(owner) && baseClass.isAssignableFrom(owner)) { - return setMinecraftClass("ChatComponentText", owner); - } - } - } catch (Exception e1) { - throw new IllegalStateException("Cannot find ChatComponentText class.", e); - } - } - throw new IllegalStateException("Cannot find ChatComponentText class."); + return getMinecraftClass("network.chat.ChatComponentText", "ChatComponentText"); } /** * Attempt to find the ChatSerializer class. + * * @return The serializer class. * @throws IllegalStateException If the class could not be found or deduced. */ public static Class getChatSerializerClass() { - try { - return getMinecraftClass("network.chat.IChatBaseComponent$ChatSerializer", - "ChatSerializer", "IChatBaseComponent$ChatSerializer"); - } catch (RuntimeException e) { - // TODO: Figure out a functional fallback - throw new IllegalStateException("Could not find ChatSerializer class.", e); - } + return getMinecraftClass("network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer"); } /** - * Retrieve the ServerPing class in Minecraft 1.7.2. + * Retrieve the ServerPing class. + * * @return The ServerPing class. */ public static Class getServerPingClass() { - try { - return getMinecraftClass("network.protocol.status.ServerPing", "ServerPing"); - } catch (RuntimeException e) { - Class statusServerInfo = PacketType.Status.Server.SERVER_INFO.getPacketClass(); - - // Find a server ping object - AbstractFuzzyMatcher> serverPingContract = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder().typeExact(String.class).build()). - field(FuzzyFieldContract.newBuilder().typeDerivedOf(getIChatBaseComponentClass()).build()). - build(). - and(getMinecraftObjectMatcher()); - - return setMinecraftClass("ServerPing", - FuzzyReflection.fromClass(statusServerInfo, true). - getField(FuzzyFieldContract.matchType(serverPingContract)).getType()); - } + return getMinecraftClass("network.protocol.status.ServerPing", "ServerPing"); } /** - * Retrieve the ServerPingServerData class in Minecraft 1.7.2. + * Retrieve the ServerPingServerData class. + * * @return The ServerPingServerData class. */ public static Class getServerPingServerDataClass() { - try { - return getMinecraftClass("network.protocol.status.ServerPing$ServerData", - "ServerPingServerData", "ServerPing$ServerData"); - } catch (RuntimeException e) { - FuzzyReflection fuzzy = FuzzyReflection.fromClass(getServerPingClass(), true); - return setMinecraftClass("ServerPingServerData", fuzzy.getFieldByType("(.*)(ServerData)(.*)").getType()); - } + return getMinecraftClass("network.protocol.status.ServerPing$ServerData", "ServerPing$ServerData"); } /** - * Retrieve the ServerPingPlayerSample class in Minecraft 1.7.2. + * Retrieve the ServerPingPlayerSample class. + * * @return The ServerPingPlayerSample class. */ public static Class getServerPingPlayerSampleClass() { - try { - return getMinecraftClass("network.protocol.status.ServerPing$ServerPingPlayerSample", - "ServerPingPlayerSample", "ServerPing$ServerPingPlayerSample"); - } catch (RuntimeException e) { - Class serverPing = getServerPingClass(); - - // Find a server ping object - AbstractFuzzyMatcher> serverPlayerContract = FuzzyClassContract.newBuilder(). - constructor(FuzzyMethodContract.newBuilder().parameterExactArray(int.class, int.class)). - field(FuzzyFieldContract.newBuilder().typeExact(getArrayClass(getGameProfileClass()))). - build(). - and(getMinecraftObjectMatcher()); - - return setMinecraftClass("ServerPingPlayerSample", getTypeFromField(serverPing, serverPlayerContract)); - } - } - - /** - * Retrieve the type of the field whose type matches. - * @param clazz - the declaring type. - * @param fieldTypeMatcher - the field type matcher. - * @return The type of the field. - */ - private static Class getTypeFromField(Class clazz, AbstractFuzzyMatcher> fieldTypeMatcher) { - final FuzzyFieldContract fieldMatcher = FuzzyFieldContract.matchType(fieldTypeMatcher); - - return FuzzyReflection.fromClass(clazz, true). - getField(fieldMatcher).getType(); - } - - /** - * Determine if this Minecraft version is using Netty. - *

    - * Spigot is ignored in this consideration. - * @return TRUE if it does, FALSE otherwise. - */ - public static boolean isUsingNetty() { - if (cachedNetty == null) { - try { - cachedNetty = getEnumProtocolClass() != null; - } catch (RuntimeException e) { - cachedNetty = false; - } - } - return cachedNetty; - } - - /** - * Retrieve the least derived class, except Object. - * @return Least derived super class. - */ - private static Class getTopmostClass(Class clazz) { - while (true) { - Class superClass = clazz.getSuperclass(); - - if (superClass == Object.class || superClass == null) - return clazz; - else - clazz = superClass; - } + return getMinecraftClass( + "network.protocol.status.ServerPing$ServerPingPlayerSample", + "ServerPing$ServerPingPlayerSample"); } /** * Retrieve the MinecraftServer class. + * * @return MinecraftServer class. */ public static Class getMinecraftServerClass() { try { - return getMinecraftClass("server.MinecraftServer","MinecraftServer"); + return getMinecraftClass("server.MinecraftServer", "MinecraftServer"); } catch (RuntimeException e) { - useFallbackServer(); - // Reset cache and try again setMinecraftClass("MinecraftServer", null); + + useFallbackServer(); return getMinecraftClass("MinecraftServer"); } } /** * Retrieve the NMS statistics class. + * * @return The statistics class. */ public static Class getStatisticClass() { - // TODO: Implement fallback return getMinecraftClass("stats.Statistic", "Statistic"); } /** * Retrieve the NMS statistic list class. + * * @return The statistic list class. */ public static Class getStatisticListClass() { - // TODO: Implement fallback return getMinecraftClass("stats.StatisticList", "StatisticList"); } - /** - * Fallback method that can determine the MinecraftServer and the ServerConfigurationManager. - */ - private static void useFallbackServer() { - // Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY) - Constructor selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")). - getConstructor(FuzzyMethodContract.newBuilder(). - parameterMatches(getMinecraftObjectMatcher(), 0). - parameterCount(2). - build() - ); - Class[] params = selected.getParameterTypes(); - - // Jackpot - two classes at the same time! - setMinecraftClass("MinecraftServer", params[0]); - setMinecraftClass("ServerConfigurationManager", params[1]); - } - /** * Retrieve the player list class (or ServerConfigurationManager), + * * @return The player list class. */ public static Class getPlayerListClass() { try { - return getMinecraftClass("server.players.PlayerList", - "ServerConfigurationManager", "PlayerList"); + return getMinecraftClass("server.players.PlayerList", "PlayerList"); } catch (RuntimeException e) { - useFallbackServer(); - // Reset cache and try again - setMinecraftClass("ServerConfigurationManager", null); - return getMinecraftClass("ServerConfigurationManager"); + setMinecraftClass("PlayerList", null); + + useFallbackServer(); + return getMinecraftClass("PlayerList"); } } /** - * Retrieve the NetLoginHandler class (or PendingConnection) - * @return The NetLoginHandler class. - */ - public static Class getNetLoginHandlerClass() { - try { - return getMinecraftClass("NetLoginHandler", "PendingConnection"); - } catch (RuntimeException e) { - Method selected = FuzzyReflection.fromClass(getPlayerListClass()). - getMethod(FuzzyMethodContract.newBuilder(). - parameterMatches( - FuzzyMatchers.matchExact(getEntityPlayerClass()).inverted(), 0 - ). - parameterExactType(String.class, 1). - parameterExactType(String.class, 2). - build() - ); - - // Save the pending connection reference - return setMinecraftClass("NetLoginHandler", selected.getParameterTypes()[0]); - } - } - - /** - * Retrieve the PlayerConnection class (or NetServerHandler) + * Retrieve the PlayerConnection class. + * * @return The PlayerConnection class. */ public static Class getPlayerConnectionClass() { - try { - return getMinecraftClass("server.network.PlayerConnection","PlayerConnection", "NetServerHandler"); - } catch (RuntimeException e) { - try { - // Use the player connection field - return setMinecraftClass("PlayerConnection", - FuzzyReflection.fromClass(getEntityPlayerClass()). - getFieldByType("playerConnection", getNetHandlerClass()).getType() - ); - - } catch (RuntimeException e1) { - // Okay, this must be on 1.7.2 - Class playerClass = getEntityPlayerClass(); - - FuzzyClassContract playerConnection = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder().typeExact(playerClass).build()). - constructor(FuzzyMethodContract.newBuilder(). - parameterCount(3). - parameterSuperOf(getMinecraftServerClass(), 0). - parameterSuperOf(getEntityPlayerClass(), 2). - build() - ). - method(FuzzyMethodContract.newBuilder(). - parameterCount(1). - parameterExactType(String.class). - build() - ). - build(); - - // If not, use duck typing - Class fieldType = FuzzyReflection.fromClass(getEntityPlayerClass(), true).getField( - FuzzyFieldContract.newBuilder().typeMatches(playerConnection).build() - ).getType(); - - return setMinecraftClass("PlayerConnection", fieldType); - } - } + return getMinecraftClass("server.network.PlayerConnection", "PlayerConnection"); } /** - * Retrieve the NetworkManager class or its interface. - * @return The NetworkManager class or its interface. + * Retrieve the NetworkManager class. + * + * @return The NetworkManager class. */ public static Class getNetworkManagerClass() { - try { - return getMinecraftClass("network.NetworkManager", "INetworkManager", "NetworkManager"); - } catch (RuntimeException e) { - Constructor selected = FuzzyReflection.fromClass(getPlayerConnectionClass()). - getConstructor(FuzzyMethodContract.newBuilder(). - parameterSuperOf(getMinecraftServerClass(), 0). - parameterSuperOf(getEntityPlayerClass(), 2). - build() - ); - - // And we're done - return setMinecraftClass("INetworkManager", selected.getParameterTypes()[1]); - } - } - - /** - * Retrieve the NetHandler class (or Connection) - * @return The NetHandler class. - */ - public static Class getNetHandlerClass() { - try { - return getMinecraftClass("NetHandler", "Connection"); - } catch (RuntimeException e) { - // Try getting the net login handler - return setMinecraftClass("NetHandler", getNetLoginHandlerClass().getSuperclass()); - } + return getMinecraftClass("network.NetworkManager", "NetworkManager"); } /** * Retrieve the NMS ItemStack class. + * * @return The ItemStack class. */ public static Class getItemStackClass() { @@ -1131,39 +729,19 @@ public class MinecraftReflection { return getMinecraftClass("world.item.ItemStack", "ItemStack"); } catch (RuntimeException e) { // Use the handle reference - return setMinecraftClass("ItemStack", - FuzzyReflection.fromClass(getCraftItemStackClass(), true).getFieldByName("handle").getType()); + return setMinecraftClass("ItemStack", FuzzyReflection.fromClass(getCraftItemStackClass(), true) + .getFieldByName("handle") + .getType()); } } /** * Retrieve the Block (NMS) class. + * * @return Block (NMS) class. */ public static Class getBlockClass() { - try { - return getMinecraftClass("world.level.block.Block", "Block"); - } catch (RuntimeException e) { - FuzzyReflection reflect = FuzzyReflection.fromClass(getItemStackClass()); - Set> candidates = new HashSet>(); - - // Minecraft objects in the constructor - for (Constructor constructor : reflect.getConstructors()) { - for (Class clazz : constructor.getParameterTypes()) { - if (isMinecraftClass(clazz)) { - candidates.add(clazz); - } - } - } - - // Useful constructors - Method selected = - reflect.getMethod(FuzzyMethodContract.newBuilder(). - parameterMatches(FuzzyMatchers.matchAnyOf(candidates)). - returnTypeExact(float.class). - build()); - return setMinecraftClass("Block", selected.getParameterTypes()[0]); - } + return getMinecraftClass("world.level.block.Block", "Block"); } public static Class getItemClass() { @@ -1180,340 +758,111 @@ public class MinecraftReflection { /** * Retrieve the WorldType class. + * * @return The WorldType class. */ public static Class getWorldTypeClass() { - try { - return getMinecraftClass("WorldType"); - } catch (RuntimeException e) { - // Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY) - Method selected = FuzzyReflection.fromClass(getMinecraftServerClass(), true). - getMethod(FuzzyMethodContract.newBuilder(). - parameterExactType(String.class, 0). - parameterExactType(String.class, 1). - parameterMatches(getMinecraftObjectMatcher()). - parameterExactType(String.class, 4). - parameterCount(5). - build() - ); - return setMinecraftClass("WorldType", selected.getParameterTypes()[3]); - } + return getMinecraftClass("WorldType"); } /** * Retrieve the DataWatcher class. + * * @return The DataWatcher class. */ public static Class getDataWatcherClass() { - try { - return getMinecraftClass("network.syncher.DataWatcher", "DataWatcher"); - } catch (RuntimeException e) { - // Describe the DataWatcher - FuzzyClassContract dataWatcherContract = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder(). - requireModifier(Modifier.STATIC). - typeDerivedOf(Map.class)). - field(FuzzyFieldContract.newBuilder(). - banModifier(Modifier.STATIC). - typeDerivedOf(Map.class)). - method(FuzzyMethodContract.newBuilder(). - parameterExactType(int.class). - parameterExactType(Object.class). - returnTypeVoid()). - build(); - FuzzyFieldContract fieldContract = FuzzyFieldContract.newBuilder(). - typeMatches(dataWatcherContract). - build(); - - // Get such a field and save the result - return setMinecraftClass("DataWatcher", - FuzzyReflection.fromClass(getEntityClass(), true). - getField(fieldContract). - getType() - ); - } - } - - /** - * Retrieves the ChunkPosition class. - * - * @return The ChunkPosition class. - */ - public static Class getChunkPositionClass() { - try { - return getMinecraftClass("ChunkPosition"); - } catch (RuntimeException e) { - return null; - } + return getMinecraftClass("network.syncher.DataWatcher", "DataWatcher"); } /** * Retrieves the BlockPosition class. - * + * * @return The BlockPosition class. */ public static Class getBlockPositionClass() { - try { - return getMinecraftClass("core.BlockPosition", "BlockPosition"); - } catch (RuntimeException e) { - try { - Class normalChunkGenerator = getCraftBukkitClass("generator.NormalChunkGenerator"); - - // BlockPosition findNearestMapFeature(World, String, BlockPosition) - FuzzyMethodContract selected = FuzzyMethodContract.newBuilder() - .banModifier(Modifier.STATIC) - .parameterMatches(getMinecraftObjectMatcher(), 0) - .parameterExactType(String.class, 1) - .parameterMatches(getMinecraftObjectMatcher(), 1) - .build(); - - return setMinecraftClass("BlockPosition", - FuzzyReflection.fromClass(normalChunkGenerator).getMethod(selected).getReturnType()); - } catch (Throwable ex) { - return null; - } - } + return getMinecraftClass("core.BlockPosition", "BlockPosition"); } /** * Retrieves the Vec3D class. + * * @return The Vec3D class. */ public static Class getVec3DClass() { - try { - return getMinecraftClass("world.phys.Vec3D", "Vec3D"); - } catch (RuntimeException e) { - // TODO: Figure out a fuzzy field contract - return null; - } - } - - /** - * Retrieve the ChunkCoordinates class. - * @return The ChunkPosition class. - */ - public static Class getChunkCoordinatesClass() { - // TODO Figure out a fallback - try { - return getMinecraftClass("ChunkCoordinates"); - } catch (RuntimeException e) { - return null; - } + return getMinecraftClass("world.phys.Vec3D", "Vec3D"); } /** * Retrieve the ChunkCoordIntPair class. + * * @return The ChunkCoordIntPair class. */ public static Class getChunkCoordIntPair() { - try { - return getMinecraftClass("world.level.ChunkCoordIntPair", "ChunkCoordIntPair"); - } catch (RuntimeException e) { - Class packet = PacketType.Play.Server.MULTI_BLOCK_CHANGE.getPacketClass(); - - AbstractFuzzyMatcher> chunkCoordIntContract = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(int.class)). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(int.class)). - method(FuzzyMethodContract.newBuilder(). - parameterExactArray(int.class). - returnDerivedOf( getChunkPositionClass() )). - build().and(getMinecraftObjectMatcher()); - - Field field = FuzzyReflection.fromClass(packet, true).getField( - FuzzyFieldContract.matchType(chunkCoordIntContract)); - return setMinecraftClass("ChunkCoordIntPair", field.getType()); - } - } - - /** - * Retrieve the WatchableObject class. Replaced by {@link #getDataWatcherItemClass()} - * @return The WatchableObject class. - */ - public static Class getWatchableObjectClass() { - return getDataWatcherItemClass(); + return getMinecraftClass("world.level.ChunkCoordIntPair", "ChunkCoordIntPair"); } /** * Retrieve the DataWatcher Item class. + * * @return The class */ public static Class getDataWatcherItemClass() { - try { - return getMinecraftClass("network.syncher.DataWatcher$Item", - "DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject"); - } catch (RuntimeException e) { - Method selected = FuzzyReflection.fromClass(getDataWatcherClass(), true). - getMethod(FuzzyMethodContract.newBuilder(). - requireModifier(Modifier.STATIC). - parameterDerivedOf(isUsingNetty() ? getPacketDataSerializerClass() : DataOutput.class, 0). - parameterMatches(getMinecraftObjectMatcher(), 1). - build()); - - // Use the second parameter - return setMinecraftClass("DataWatcher$Item", selected.getParameterTypes()[1]); - } + return getMinecraftClass("network.syncher.DataWatcher$Item", "DataWatcher$Item", "DataWatcher$WatchableObject"); } public static Class getDataWatcherObjectClass() { - try { - return getMinecraftClass("network.syncher.DataWatcherObject", "DataWatcherObject"); - } catch (RuntimeException ex) { - return null; - } + return getNullableNMS("network.syncher.DataWatcherObject", "DataWatcherObject"); } public static boolean watcherObjectExists() { if (cachedWatcherObject == null) { - try { - return cachedWatcherObject = getDataWatcherObjectClass() != null; - } catch (Throwable ex) { - return cachedWatcherObject = false; - } + cachedWatcherObject = getDataWatcherObjectClass() != null; } return cachedWatcherObject; } public static Class getDataWatcherSerializerClass() { - // TODO Implement a fallback return getNullableNMS("network.syncher.DataWatcherSerializer", "DataWatcherSerializer"); } public static Class getDataWatcherRegistryClass() { - // TODO Implement a fallback return getMinecraftClass("network.syncher.DataWatcherRegistry", "DataWatcherRegistry"); } public static Class getMinecraftKeyClass() { - // TODO Implement a fallback return getMinecraftClass("resources.MinecraftKey", "MinecraftKey"); } public static Class getMobEffectListClass() { - // TODO Implement a fallback return getMinecraftClass("world.effect.MobEffectList", "MobEffectList"); } public static Class getSoundEffectClass() { - try { - return getMinecraftClass("sounds.SoundEffect", "SoundEffect"); - } catch (RuntimeException ex) { - FuzzyReflection fuzzy = FuzzyReflection.fromClass(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), true); - Field field = fuzzy.getFieldByType("(.*)(Sound)(.*)"); - return setMinecraftClass("SoundEffect", field.getType()); - } + return getNullableNMS("sounds.SoundEffect", "SoundEffect"); } /** * Retrieve the ServerConnection abstract class. + * * @return The ServerConnection class. */ public static Class getServerConnectionClass() { - try { - return getMinecraftClass("server.network.ServerConnection", "ServerConnection"); - } catch (RuntimeException e) { - Method selected = null; - FuzzyClassContract.Builder serverConnectionContract = FuzzyClassContract.newBuilder(). - constructor(FuzzyMethodContract.newBuilder(). - parameterExactType(getMinecraftServerClass()). - parameterCount(1)); - - if (isUsingNetty()) { - serverConnectionContract. - method(FuzzyMethodContract.newBuilder(). - parameterDerivedOf(InetAddress.class, 0). - parameterDerivedOf(int.class, 1). - parameterCount(2) - ); - - selected = FuzzyReflection.fromClass(getMinecraftServerClass()). - getMethod(FuzzyMethodContract.newBuilder(). - requireModifier(Modifier.PUBLIC). - returnTypeMatches(serverConnectionContract.build()). - build()); - - } else { - serverConnectionContract. - method(FuzzyMethodContract.newBuilder(). - parameterExactType(getPlayerConnectionClass())); - - selected = FuzzyReflection.fromClass(getMinecraftServerClass()). - getMethod(FuzzyMethodContract.newBuilder(). - requireModifier(Modifier.ABSTRACT). - returnTypeMatches(serverConnectionContract.build()). - build()); - } - - // Use the return type - return setMinecraftClass("ServerConnection", selected.getReturnType()); - } + return getMinecraftClass("server.network.ServerConnection", "ServerConnection"); } /** * Retrieve the NBT base class. + * * @return The NBT base class. */ public static Class getNBTBaseClass() { - try { - return getMinecraftClass("nbt.NBTBase", "NBTBase"); - } catch (RuntimeException e) { - Class nbtBase = null; - - if (isUsingNetty()) { - FuzzyClassContract tagCompoundContract = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Map.class)). - method(FuzzyMethodContract.newBuilder(). - parameterDerivedOf(DataOutput.class). - parameterCount(1)). - build(); - - Method selected = FuzzyReflection.fromClass(getPacketDataSerializerClass()). - getMethod(FuzzyMethodContract.newBuilder(). - banModifier(Modifier.STATIC). - parameterCount(1). - parameterMatches(tagCompoundContract). - returnTypeVoid(). - build() - ); - nbtBase = selected.getParameterTypes()[0].getSuperclass(); - - } else { - FuzzyClassContract tagCompoundContract = FuzzyClassContract.newBuilder(). - constructor(FuzzyMethodContract.newBuilder(). - parameterExactType(String.class). - parameterCount(1)). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Map.class)). - build(); - - Method selected = FuzzyReflection.fromClass(getPacketClass()). - getMethod(FuzzyMethodContract.newBuilder(). - requireModifier(Modifier.STATIC). - parameterSuperOf(DataInputStream.class). - parameterCount(1). - returnTypeMatches(tagCompoundContract). - build() - ); - nbtBase = selected.getReturnType().getSuperclass(); - } - - // That can't be correct - if (nbtBase == null || nbtBase.equals(Object.class)) { - throw new IllegalStateException("Unable to find NBT base class: " + nbtBase); - } - - // Use the return type here too - return setMinecraftClass("NBTBase", nbtBase); - } - } + return getMinecraftClass("nbt.NBTBase", "NBTBase"); + } /** * Retrieve the NBT read limiter class. - *

    - * This is only supported in 1.7.8 (released 2014) and higher. + * * @return The NBT read limiter. */ public static Class getNBTReadLimiterClass() { @@ -1522,205 +871,84 @@ public class MinecraftReflection { /** * Retrieve the NBT Compound class. + * * @return The NBT Compond class. */ public static Class getNBTCompoundClass() { - try { - return getMinecraftClass("nbt.NBTTagCompound","NBTTagCompound"); - } catch (RuntimeException e) { - return setMinecraftClass( - "NBTTagCompound", - NbtFactory.ofWrapper(NbtType.TAG_COMPOUND, "Test").getHandle().getClass() - ); - } + return getMinecraftClass("nbt.NBTTagCompound", "NBTTagCompound"); } /** * Retrieve the EntityTracker (NMS) class. + * * @return EntityTracker class. */ public static Class getEntityTrackerClass() { - try { - return getMinecraftClass("server.level.PlayerChunkMap$EntityTracker", - "EntityTracker", "PlayerChunkMap$EntityTracker"); - } catch (RuntimeException e) { - FuzzyClassContract entityTrackerContract = FuzzyClassContract.newBuilder(). - field(FuzzyFieldContract.newBuilder(). - typeDerivedOf(Set.class)). - method(FuzzyMethodContract.newBuilder(). - parameterSuperOf(MinecraftReflection.getEntityClass()). - parameterCount(1). - returnTypeVoid()). - method(FuzzyMethodContract.newBuilder(). - parameterSuperOf(MinecraftReflection.getEntityClass(), 0). - parameterSuperOf(int.class, 1). - parameterSuperOf(int.class, 2). - parameterCount(3). - returnTypeVoid()). - build(); - - Field selected = FuzzyReflection.fromClass(MinecraftReflection.getWorldServerClass(), true). - getField(FuzzyFieldContract.newBuilder(). - typeMatches(entityTrackerContract). - build() - ); - - // Go by the defined type of this field - return setMinecraftClass("EntityTracker", selected.getType()); - } + return getMinecraftClass("server.level.PlayerChunkMap$EntityTracker", "EntityTracker"); } /** * Retrieve the attribute snapshot class. *

    * This stores the final value of an attribute, along with all the associated computational steps. + * * @return The attribute snapshot class. */ public static Class getAttributeSnapshotClass() { - try { - return getMinecraftClass("network.protocol.game.PacketPlayOutUpdateAttributes$AttributeSnapshot", - "AttributeSnapshot", "PacketPlayOutUpdateAttributes$AttributeSnapshot"); - } catch (RuntimeException ex) { - try { - // It should be the parameter of a list in the update attributes packet - FuzzyReflection fuzzy = FuzzyReflection.fromClass(PacketType.Play.Server.UPDATE_ATTRIBUTES.getPacketClass(), true); - Field field = fuzzy.getFieldByType("attributes", Collection.class); - Type param = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; - return setMinecraftClass("AttributeSnapshot", (Class) param); - } catch (Throwable ex1) { - return getMinecraftClass("AttributeSnapshot"); - } - } + return getMinecraftClass( + "network.protocol.game.PacketPlayOutUpdateAttributes$AttributeSnapshot", + "AttributeSnapshot", + "PacketPlayOutUpdateAttributes$AttributeSnapshot"); } /** * Retrieve the IntHashMap class. + * * @return IntHashMap class. */ public static Class getIntHashMapClass() { - try { - return getMinecraftClass("IntHashMap"); - } catch (RuntimeException e) { - final Class parent = getEntityTrackerClass(); - - // Expected structure of a IntHashMap - final FuzzyClassContract intHashContract = FuzzyClassContract.newBuilder(). - // add(int key, Object value) - method(FuzzyMethodContract.newBuilder(). - parameterCount(2). - parameterExactType(int.class, 0). - parameterExactType(Object.class, 1).requirePublic() - ). - // Object get(int key) - method(FuzzyMethodContract.newBuilder(). - parameterCount(1). - parameterExactType(int.class). - returnTypeExact(Object.class).requirePublic() - ). - // Finally, there should be an array of some kind - field(FuzzyFieldContract.newBuilder(). - typeMatches(FuzzyMatchers.matchArray(FuzzyMatchers.matchAll())) - ). - build(); - - final AbstractFuzzyMatcher intHashField = FuzzyFieldContract.newBuilder(). - typeMatches(getMinecraftObjectMatcher().and(intHashContract)). - build(); - - // Use the type of the first field that matches - return setMinecraftClass("IntHashMap", FuzzyReflection.fromClass(parent).getField(intHashField).getType()); - } + return getNullableNMS("IntHashMap"); } /** * Retrieve the attribute modifier class. + * * @return Attribute modifier class. */ public static Class getAttributeModifierClass() { - try { - return getMinecraftClass("world.entity.ai.attributes.AttributeModifier", "AttributeModifier"); - } catch (RuntimeException e) { - getAttributeSnapshotClass(); - - // Reset cache and try again - setMinecraftClass("AttributeModifier", null); - return getMinecraftClass("AttributeModifier"); - } + return getMinecraftClass("world.entity.ai.attributes.AttributeModifier", "AttributeModifier"); } /** * Retrieve the net.minecraft.server.MobEffect class. + * * @return The mob effect class. */ public static Class getMobEffectClass() { - try { - return getMinecraftClass("world.effect.MobEffect", "MobEffect"); - } catch (RuntimeException e) { - // It is the second parameter in Packet41MobEffect - Class packet = PacketType.Play.Server.ENTITY_EFFECT.getPacketClass(); - Constructor constructor = FuzzyReflection.fromClass(packet).getConstructor( - FuzzyMethodContract.newBuilder(). - parameterCount(2). - parameterExactType(int.class, 0). - parameterMatches(getMinecraftObjectMatcher(), 1). - build() - ); - return setMinecraftClass("MobEffect", constructor.getParameterTypes()[1]); - } + return getMinecraftClass("world.effect.MobEffect", "MobEffect"); } /** * Retrieve the packet data serializer class that overrides ByteBuf. + * * @return The data serializer class. */ public static Class getPacketDataSerializerClass() { - try { - return getMinecraftClass("network.PacketDataSerializer", "PacketDataSerializer"); - } catch (RuntimeException e) { - Class packet = getPacketClass(); - Method method = FuzzyReflection.fromClass(packet).getMethod( - FuzzyMethodContract.newBuilder(). - parameterCount(1). - parameterDerivedOf(getByteBufClass()). - returnTypeVoid(). - build() - ); - return setMinecraftClass("PacketDataSerializer", method.getParameterTypes()[0]); - } + return getMinecraftClass("network.PacketDataSerializer", "PacketDataSerializer"); } /** * Retrieve the NBTCompressedStreamTools class. + * * @return The NBTCompressedStreamTools class. */ public static Class getNbtCompressedStreamToolsClass() { - try { - return getMinecraftClass("nbt.NBTCompressedStreamTools", "NBTCompressedStreamTools"); - } catch (RuntimeException e) { - Class packetSerializer = getPacketDataSerializerClass(); - - // Get the write NBT compound method - Method writeNbt = FuzzyReflection.fromClass(packetSerializer). - getMethodByParameters("writeNbt", getNBTCompoundClass()); - - try { - // Now -- we inspect all the method calls within that method, and use the first foreign Minecraft class - for (AsmMethod method : ClassAnalyser.getDefault().getMethodCalls(writeNbt)) { - Class owner = method.getOwnerClass(); - - if (!packetSerializer.equals(owner) && isMinecraftClass(owner)) { - return setMinecraftClass("NBTCompressedStreamTools", owner); - } - } - } catch (Exception e1) { - throw new RuntimeException("Unable to analyse class.", e1); - } - throw new IllegalArgumentException("Unable to find NBTCompressedStreamTools."); - } + return getMinecraftClass("nbt.NBTCompressedStreamTools", "NBTCompressedStreamTools"); } /** * Retrieve the NMS tile entity class. + * * @return The tile entity class. */ public static Class getTileEntityClass() { @@ -1729,40 +957,39 @@ public class MinecraftReflection { /** * Retrieve the Gson class used by Minecraft. + * * @return The Gson class. */ public static Class getMinecraftGsonClass() { - try { - return getMinecraftLibraryClass("com.google.gson.Gson"); - } catch (RuntimeException e) { - Class match = FuzzyReflection.fromClass(PacketType.Status.Server.SERVER_INFO.getPacketClass(), true) - .getFieldByType("(.*)(google.gson.Gson)").getType(); - return setMinecraftLibraryClass("com.google.gson.Gson", match); - } + return getMinecraftLibraryClass("com.google.gson.Gson"); } /** * Retrieve the ItemStack[] class. + * * @return The ItemStack[] class. */ public static Class getItemStackArrayClass() { - if (itemStackArrayClass == null) + if (itemStackArrayClass == null) { itemStackArrayClass = getArrayClass(getItemStackClass()); + } return itemStackArrayClass; } /** * Retrieve the array class of a given component type. + * * @param componentType - type of each element in the array. * @return The class of the array. */ public static Class getArrayClass(Class componentType) { - // Bit of a hack, but it works + // A bit of a hack, but it works return Array.newInstance(componentType, 0).getClass(); } /** * Retrieve the CraftItemStack class. + * * @return The CraftItemStack class. */ public static Class getCraftItemStackClass() { @@ -1771,6 +998,7 @@ public class MinecraftReflection { /** * Retrieve the CraftPlayer class. + * * @return CraftPlayer class. */ public static Class getCraftPlayerClass() { @@ -1779,6 +1007,7 @@ public class MinecraftReflection { /** * Retrieve the CraftWorld class. + * * @return The CraftWorld class. */ public static Class getCraftWorldClass() { @@ -1787,6 +1016,7 @@ public class MinecraftReflection { /** * Retrieve the CraftEntity class. + * * @return CraftEntity class. */ public static Class getCraftEntityClass() { @@ -1795,6 +1025,7 @@ public class MinecraftReflection { /** * Retrieve the CraftChatMessage introduced in 1.7.2 + * * @return The CraftChatMessage class. */ public static Class getCraftMessageClass() { @@ -1803,15 +1034,18 @@ public class MinecraftReflection { /** * Retrieve the PlayerInfoData class in 1.8. + * * @return The PlayerInfoData class */ public static Class getPlayerInfoDataClass() { - return getMinecraftClass("network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", + return getMinecraftClass( + "network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", "PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData"); } /** * Retrieves the entity use action class in 1.17. + * * @return The EntityUseAction class */ public static Class getEnumEntityUseActionClass() { @@ -1821,6 +1055,7 @@ public class MinecraftReflection { /** * Get a method accessor to get the actual use action out of the wrapping EnumEntityUseAction in 1.17. + * * @return a method accessor to get the actual use action */ public static MethodAccessor getEntityUseActionEnumMethodAccessor() { @@ -1858,6 +1093,7 @@ public class MinecraftReflection { /** * Determine if the given object is a PlayerInfoData. + * * @param obj - the given object. * @return TRUE if it is, FALSE otherwise. */ @@ -1867,6 +1103,7 @@ public class MinecraftReflection { /** * Retrieve the IBlockData class in 1.8. + * * @return The IBlockData class */ public static Class getIBlockDataClass() { @@ -1875,6 +1112,7 @@ public class MinecraftReflection { /** * Retrieve the MultiBlockChangeInfo class in 1.8. + * * @return The MultiBlockChangeInfo class */ public static Class getMultiBlockChangeInfoClass() { @@ -1883,6 +1121,7 @@ public class MinecraftReflection { /** * Retrieve the MultiBlockChangeInfo array class in 1.8. + * * @return The MultiBlockChangeInfo array class */ public static Class getMultiBlockChangeInfoArrayClass() { @@ -1891,10 +1130,11 @@ public class MinecraftReflection { /** * Retrieve the PacketPlayOutGameStateChange.a class, aka GameState in 1.16 + * * @return The GameState class */ public static Class getGameStateClass() { - // it's called "a" so there's not a whole lot we can do to identify it + // it's called "a" so there's not a lot we can do to identify it Class packetClass = PacketType.Play.Server.GAME_STATE_CHANGE.getPacketClass(); return packetClass.getClasses()[0]; } @@ -1910,11 +1150,11 @@ public class MinecraftReflection { public static MethodAccessor getNonNullListCreateAccessor() { try { Class nonNullListType = MinecraftReflection.getNonNullListClass(); - return Accessors.getMethodAccessor(FuzzyReflection.fromClass(nonNullListType).getMethod( - FuzzyMethodContract.newBuilder() - .returnTypeExact(nonNullListType) - .requireModifier(Modifier.STATIC) - .build())); + Method method = FuzzyReflection.fromClass(nonNullListType).getMethod(FuzzyMethodContract.newBuilder() + .returnTypeExact(nonNullListType) + .requireModifier(Modifier.STATIC) + .build()); + return Accessors.getMethodAccessor(method); } catch (Exception ex) { return null; } @@ -1928,19 +1168,10 @@ public class MinecraftReflection { return getMinecraftClass("core.SectionPosition", "SectionPosition"); } - // ---- ItemStack conversions - private static Object itemStackAir = null; - private static Method asNMSCopy = null; - private static Method asCraftMirror = null; - - private static Boolean nullEnforced = null; - private static Method isEmpty = null; - /** - * Retrieves the Bukkit equivalent of a NMS ItemStack. This method should - * preserve NBT data and will never return null when supplied with a valid - * ItemStack. Empty ItemStacks are treated as AIR. - * + * Retrieves the Bukkit equivalent of a NMS ItemStack. This method should preserve NBT data and will never return null + * when supplied with a valid ItemStack. Empty ItemStacks are treated as AIR. + * * @param generic NMS ItemStack * @return The Bukkit equivalent */ @@ -1960,7 +1191,7 @@ public class MinecraftReflection { } // If not, convert it to one - Object nmsStack = getMinecraftItemStack((ItemStack) generic); + Object nmsStack = getMinecraftItemStack(bukkit); return getBukkitItemStack(nmsStack); } @@ -1972,7 +1203,7 @@ public class MinecraftReflection { try { // Check null enforcement - 1.11 behavior if (nullEnforced == null) { - isEmpty = getItemStackClass().getMethod("isEmpty"); + isEmpty = Accessors.getMethodAccessor(getItemStackClass().getMethod("isEmpty")); nullEnforced = true; } @@ -1987,7 +1218,8 @@ public class MinecraftReflection { if (asCraftMirror == null) { try { - asCraftMirror = getCraftItemStackClass().getMethod("asCraftMirror", getItemStackClass()); + Method asMirrorMethod = getCraftItemStackClass().getMethod("asCraftMirror", getItemStackClass()); + asCraftMirror = Accessors.getMethodAccessor(asMirrorMethod); } catch (ReflectiveOperationException ex) { throw new RuntimeException("Failed to obtain CraftItemStack.asCraftMirror", ex); } @@ -1996,23 +1228,23 @@ public class MinecraftReflection { try { // Convert to a craft mirror to preserve NBT data return (ItemStack) asCraftMirror.invoke(nullEnforced, generic); - } catch (ReflectiveOperationException ex) { + } catch (IllegalStateException ex) { throw new RuntimeException("Failed to obtain craft mirror of " + generic, ex); } } /** - * Retrieves the NMS equivalent of a Bukkit ItemStack. This method will - * never return null and should preserve NBT data. Null inputs are treated - * as empty (AIR) ItemStacks. - * + * Retrieves the NMS equivalent of a Bukkit ItemStack. This method will never return null and should preserve NBT + * data. Null inputs are treated as empty (AIR) ItemStacks. + * * @param specific Bukkit ItemStack * @return The NMS equivalent */ public static Object getMinecraftItemStack(ItemStack specific) { if (asNMSCopy == null) { try { - asNMSCopy = getCraftItemStackClass().getMethod("asNMSCopy", ItemStack.class); + Method asNmsCopyMethod = getCraftItemStackClass().getMethod("asNMSCopy", ItemStack.class); + asNMSCopy = Accessors.getMethodAccessor(asNmsCopyMethod); } catch (ReflectiveOperationException ex) { throw new RuntimeException("Failed to obtain CraftItemStack.asNMSCopy", ex); } @@ -2020,7 +1252,7 @@ public class MinecraftReflection { if (is(getCraftItemStackClass(), specific)) { // If it's already a CraftItemStack, use its handle - Object unwrapped = new BukkitUnwrapper().unwrapItem(specific); + Object unwrapped = BukkitUnwrapper.getInstance().unwrapItem(specific); if (unwrapped != null) { return unwrapped; } else { @@ -2035,19 +1267,20 @@ public class MinecraftReflection { try { // If not, grab a NMS copy return asNMSCopy.invoke(null, specific); - } catch (ReflectiveOperationException ex) { + } catch (IllegalStateException ex) { throw new RuntimeException("Failed to make NMS copy of " + specific, ex); } } /** * Retrieve the given class by name. + * * @param className - name of the class. * @return The class. */ private static Class getClass(String className) { try { - return MinecraftReflection.class.getClassLoader().loadClass(className); + return getClassSource().loadClass(className); } catch (ClassNotFoundException e) { throw new RuntimeException("Cannot find class " + className, e); } @@ -2055,26 +1288,32 @@ public class MinecraftReflection { /** * Retrieve the class object of a specific CraftBukkit class. + * * @param className - the specific CraftBukkit class. * @return Class object. * @throws RuntimeException If we are unable to find the given class. */ public static Class getCraftBukkitClass(String className) { - if (craftbukkitPackage == null) + if (craftbukkitPackage == null) { craftbukkitPackage = new CachedPackage(getCraftBukkitPackage(), getClassSource()); + } + return craftbukkitPackage.getPackageClass(className) .orElseThrow(() -> new RuntimeException("Failed to find CraftBukkit class: " + className)); } - + /** * Retrieve the class object of a specific Minecraft class. + * * @param className - the specific Minecraft class. * @return Class object. * @throws RuntimeException If we are unable to find the given class. */ public static Class getMinecraftClass(String className) { - if (minecraftPackage == null) + if (minecraftPackage == null) { minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); + } + return minecraftPackage.getPackageClass(className) .orElseThrow(() -> new RuntimeException("Failed to find NMS class: " + className)); } @@ -2089,129 +1328,113 @@ public class MinecraftReflection { /** * Set the class object for the specific Minecraft class. + * * @param className - name of the Minecraft class. - * @param clazz - the new class object. + * @param clazz - the new class object. * @return The provided clazz object. */ private static Class setMinecraftClass(String className, Class clazz) { - if (minecraftPackage == null) + if (minecraftPackage == null) { minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); + } + minecraftPackage.setPackageClass(className, clazz); return clazz; } /** * Retrieve the current class source. + * * @return The class source. */ private static ClassSource getClassSource() { - ErrorReporter reporter = ProtocolLibrary.getErrorReporter(); - - // Lazy pattern again - if (classSource == null) { - // Attempt to use MCPC - try { - return classSource = new RemappedClassSource().initialize(); - } catch (RemapperUnavailableException e) { - if (e.getReason() != Reason.MCPC_NOT_PRESENT) - reporter.reportWarning(MinecraftReflection.class, Report.newBuilder(REPORT_CANNOT_FIND_MCPC_REMAPPER)); - } catch (Exception e) { - reporter.reportWarning(MinecraftReflection.class, Report.newBuilder(REPORT_CANNOT_LOAD_CPC_REMAPPER)); - } - - // Just use the default class loader - classSource = ClassSource.fromClassLoader(); - } - - return classSource; + return CLASS_SOURCE; } /** * Retrieve the first class that matches a specified Minecraft name. + * * @param className - the specific Minecraft class. - * @param aliases - alternative names for this Minecraft class. + * @param aliases - alternative names for this 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 e) { - Class success = null; + if (minecraftPackage == null) { + minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); + } - // Try every alias too + return minecraftPackage.getPackageClass(className).orElseGet(() -> { + Class resolved = null; for (String alias : aliases) { - try { - success = getMinecraftClass(alias); + // try to resolve the class and stop searching if we found it + resolved = minecraftPackage.getPackageClass(alias).orElse(null); + if (resolved != null) { break; - } catch (RuntimeException e1) { - // e1.printStackTrace(); } } - 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))); + // if we resolved the class cache it and return the result + if (resolved != null) { + minecraftPackage.setPackageClass(className, resolved); + return resolved; } - } + + // unable to find the class + throw new RuntimeException(String.format("Unable to find %s (%s)", className, String.join(", ", aliases))); + }); } /** * Retrieve the class object of a specific Minecraft library class. + * * @param className - the specific library Minecraft class. * @return Class object. * @throws RuntimeException If we are unable to find the given class. */ public static Class getMinecraftLibraryClass(String className) { - if (libraryPackage == null) + if (libraryPackage == null) { libraryPackage = new CachedPackage("", getClassSource()); + } + return libraryPackage.getPackageClass(className) .orElseThrow(() -> new RuntimeException("Failed to find class: " + className)); } /** * Set the class object for the specific library class. + * * @param className - name of the Minecraft library class. - * @param clazz - the new class object. + * @param clazz - the new class object. * @return The provided clazz object. */ private static Class setMinecraftLibraryClass(String className, Class clazz) { - if (libraryPackage == null) + if (libraryPackage == null) { libraryPackage = new CachedPackage("", getClassSource()); + } + libraryPackage.setPackageClass(className, clazz); return clazz; } /** * 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(); - } - /** * Retrieve an instance of the packet data serializer wrapper. + * * @param buffer - the buffer. * @return The instance. */ public static Object getPacketDataSerializer(Object buffer) { - Class packetSerializer = getPacketDataSerializerClass(); - try { + Class packetSerializer = getPacketDataSerializerClass(); return packetSerializer.getConstructor(getByteBufClass()).newInstance(buffer); } catch (Exception e) { throw new RuntimeException("Cannot construct packet serializer.", e); @@ -2299,4 +1522,21 @@ public class MinecraftReflection { return clazz; } } + + /** + * Fallback method that can determine the MinecraftServer and the ServerConfigurationManager. + */ + private static void useFallbackServer() { + // Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY) + Constructor selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")) + .getConstructor(FuzzyMethodContract.newBuilder() + .parameterMatches(getMinecraftObjectMatcher(), 0) + .parameterCount(2) + .build()); + Class[] params = selected.getParameterTypes(); + + // Jackpot - two classes at the same time! + setMinecraftClass("MinecraftServer", params[0]); + setMinecraftClass("PlayerList", params[1]); + } } diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java b/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java index 4e2b3a80..c849f73e 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java @@ -17,27 +17,105 @@ package com.comphenix.protocol.utility; +import com.comphenix.protocol.ProtocolLibrary; +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Ordering; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.Locale; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.bukkit.Bukkit; import org.bukkit.Server; -import com.comphenix.protocol.ProtocolLibrary; -import com.google.common.base.Objects; -import com.google.common.collect.ComparisonChain; -import com.google.common.collect.Ordering; - /** * Determine the current Minecraft version. - * + * * @author Kristian */ -public class MinecraftVersion implements Comparable, Serializable { - private static final long serialVersionUID = 1L; +public final class MinecraftVersion implements Comparable, Serializable { + + /** + * Version 1.19 - the wild update + */ + public static final MinecraftVersion WILD_UPDATE = new MinecraftVersion("1.19"); + /** + * Version 1.18 - caves and cliffs part 2 + */ + public static final MinecraftVersion CAVES_CLIFFS_2 = new MinecraftVersion("1.18"); + /** + * Version 1.17 - caves and cliffs part 1 + */ + public static final MinecraftVersion CAVES_CLIFFS_1 = new MinecraftVersion("1.17"); + /** + * Version 1.16.2 - breaking change to the nether update + */ + public static final MinecraftVersion NETHER_UPDATE_2 = new MinecraftVersion("1.16.2"); + /** + * Version 1.16.0 - the nether update + */ + public static final MinecraftVersion NETHER_UPDATE = new MinecraftVersion("1.16"); + /** + * Version 1.15 - the bee update + */ + public static final MinecraftVersion BEE_UPDATE = new MinecraftVersion("1.15"); + /** + * Version 1.14 - village and pillage update. + */ + public static final MinecraftVersion VILLAGE_UPDATE = new MinecraftVersion("1.14"); + /** + * Version 1.13 - update aquatic. + */ + public static final MinecraftVersion AQUATIC_UPDATE = new MinecraftVersion("1.13"); + /** + * Version 1.12 - the world of color update. + */ + public static final MinecraftVersion COLOR_UPDATE = new MinecraftVersion("1.12"); + /** + * Version 1.11 - the exploration update. + */ + public static final MinecraftVersion EXPLORATION_UPDATE = new MinecraftVersion("1.11"); + /** + * Version 1.10 - the frostburn update. + */ + public static final MinecraftVersion FROSTBURN_UPDATE = new MinecraftVersion("1.10"); + /** + * Version 1.9 - the combat update. + */ + public static final MinecraftVersion COMBAT_UPDATE = new MinecraftVersion("1.9"); + /** + * Version 1.8 - the "bountiful" update. + */ + public static final MinecraftVersion BOUNTIFUL_UPDATE = new MinecraftVersion("1.8"); + /** + * Version 1.7.8 - the update that changed the skin format (and distribution - R.I.P. player disguise) + */ + public static final MinecraftVersion SKIN_UPDATE = new MinecraftVersion("1.7.8"); + /** + * Version 1.7.2 - the update that changed the world. + */ + public static final MinecraftVersion WORLD_UPDATE = new MinecraftVersion("1.7.2"); + /** + * Version 1.6.1 - the horse update. + */ + public static final MinecraftVersion HORSE_UPDATE = new MinecraftVersion("1.6.1"); + /** + * Version 1.5.0 - the redstone update. + */ + public static final MinecraftVersion REDSTONE_UPDATE = new MinecraftVersion("1.5.0"); + /** + * Version 1.4.2 - the scary update (Wither Boss). + */ + public static final MinecraftVersion SCARY_UPDATE = new MinecraftVersion("1.4.2"); + + /** + * The latest release version of minecraft. + */ + public static final MinecraftVersion LATEST = CAVES_CLIFFS_2; + + // used when serializing + private static final long serialVersionUID = -8695133558996459770L; /** * Regular expression used to parse version strings. @@ -45,108 +123,23 @@ public class MinecraftVersion implements Comparable, Serializa private static final Pattern VERSION_PATTERN = Pattern.compile(".*\\(.*MC.\\s*([a-zA-z0-9\\-.]+).*"); /** - * Version 1.19 - the wild update + * The current version of minecraft, lazy initialized by MinecraftVersion.currentVersion() */ - public static final MinecraftVersion WILD_UPDATE = new MinecraftVersion("1.19"); + private static MinecraftVersion currentVersion; - /** - * Version 1.18 - caves and cliffs part 2 - */ - public static final MinecraftVersion CAVES_CLIFFS_2 = new MinecraftVersion("1.18"); - - /** - * Version 1.17 - caves and cliffs part 1 - */ - public static final MinecraftVersion CAVES_CLIFFS_1 = new MinecraftVersion("1.17"); - - /** - * Version 1.16.2 - breaking change to the nether update - */ - public static final MinecraftVersion NETHER_UPDATE_2 = new MinecraftVersion("1.16.2"); - - /** - * Version 1.16.0 - the nether update - */ - public static final MinecraftVersion NETHER_UPDATE = new MinecraftVersion("1.16"); - - /** - * Version 1.15 - the bee update - */ - public static final MinecraftVersion BEE_UPDATE = new MinecraftVersion("1.15"); - - /** - * Version 1.14 - village and pillage update. - */ - public static final MinecraftVersion VILLAGE_UPDATE = new MinecraftVersion("1.14"); - - /** - * Version 1.13 - update aquatic. - */ - public static final MinecraftVersion AQUATIC_UPDATE = new MinecraftVersion("1.13"); - - /** - * Version 1.12 - the world of color update. - */ - public static final MinecraftVersion COLOR_UPDATE = new MinecraftVersion("1.12"); - - /** - * Version 1.11 - the exploration update. - */ - public static final MinecraftVersion EXPLORATION_UPDATE = new MinecraftVersion("1.11"); - - /** - * Version 1.10 - the frostburn update. - */ - public static final MinecraftVersion FROSTBURN_UPDATE = new MinecraftVersion("1.10"); - - /** - * Version 1.9 - the combat update. - */ - public static final MinecraftVersion COMBAT_UPDATE = new MinecraftVersion("1.9"); - - /** - * Version 1.8 - the "bountiful" update. - */ - public static final MinecraftVersion BOUNTIFUL_UPDATE = new MinecraftVersion("1.8"); - - /** - * Version 1.7.8 - the update that changed the skin format (and distribution - R.I.P. player disguise) - */ - public static final MinecraftVersion SKIN_UPDATE = new MinecraftVersion("1.7.8"); - - /** - * Version 1.7.2 - the update that changed the world. - */ - public static final MinecraftVersion WORLD_UPDATE = new MinecraftVersion("1.7.2"); - - /** - * Version 1.6.1 - the horse update. - */ - public static final MinecraftVersion HORSE_UPDATE = new MinecraftVersion("1.6.1"); - - /** - * Version 1.5.0 - the redstone update. - */ - public static final MinecraftVersion REDSTONE_UPDATE = new MinecraftVersion("1.5.0"); - - /** - * Version 1.4.2 - the scary update (Wither Boss). - */ - public static final MinecraftVersion SCARY_UPDATE = new MinecraftVersion("1.4.2"); - private final int major; private final int minor; private final int build; - private Boolean atLeast; - // The development stage private final String development; - + // Snapshot? private final SnapshotVersion snapshot; - + private volatile Boolean atCurrentOrAbove; + /** * Determine the current Minecraft version. + * * @param server - the Bukkit server that will be used to examine the MC version. */ public MinecraftVersion(Server server) { @@ -155,30 +148,32 @@ public class MinecraftVersion implements Comparable, Serializa /** * Construct a version object from the format major.minor.build, or the snapshot format. + * * @param versionOnly - the version in text form. */ public MinecraftVersion(String versionOnly) { this(versionOnly, true); } - + /** * Construct a version format from the standard release version or the snapshot verison. - * @param versionOnly - the version. + * + * @param versionOnly - the version. * @param parseSnapshot - TRUE to parse the snapshot, FALSE otherwise. */ private MinecraftVersion(String versionOnly, boolean parseSnapshot) { String[] section = versionOnly.split("-"); SnapshotVersion snapshot = null; int[] numbers = new int[3]; - + try { - numbers = parseVersion(section[0]); - + numbers = this.parseVersion(section[0]); } catch (NumberFormatException cause) { // Skip snapshot parsing - if (!parseSnapshot) + if (!parseSnapshot) { throw cause; - + } + try { // Determine if the snapshot is newer than the current release version snapshot = new SnapshotVersion(section[0]); @@ -186,25 +181,25 @@ public class MinecraftVersion implements Comparable, Serializa MinecraftVersion latest = new MinecraftVersion(ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION, false); boolean newer = snapshot.getSnapshotDate().compareTo( - format.parse(ProtocolLibrary.MINECRAFT_LAST_RELEASE_DATE)) > 0; - - numbers[0] = latest.getMajor(); - numbers[1] = latest.getMinor() + (newer ? 1 : -1); - numbers[2] = 0; + format.parse(ProtocolLibrary.MINECRAFT_LAST_RELEASE_DATE)) > 0; + + numbers[0] = latest.getMajor(); + numbers[1] = latest.getMinor() + (newer ? 1 : -1); } catch (Exception e) { throw new IllegalStateException("Cannot parse " + section[0], e); } } - + this.major = numbers[0]; this.minor = numbers[1]; this.build = numbers[2]; this.development = section.length > 1 ? section[1] : (snapshot != null ? "snapshot" : null); this.snapshot = snapshot; } - + /** * Construct a version object directly. + * * @param major - major version number. * @param minor - minor version number. * @param build - build version number. @@ -212,12 +207,13 @@ public class MinecraftVersion implements Comparable, Serializa 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 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) { @@ -228,158 +224,26 @@ public class MinecraftVersion implements Comparable, Serializa this.snapshot = null; } - private int[] parseVersion(String version) { - String[] elements = version.split("\\."); - int[] numbers = new int[3]; - - // Make sure it's even a valid version - if (elements.length < 1) - throw new IllegalStateException("Corrupt MC version: " + version); - - // 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()); - return numbers; - } - - /** - * Major version number - * @return Current major version number. - */ - public int getMajor() { - return major; - } - - /** - * Minor version number - * @return Current minor version number. - */ - public int getMinor() { - return minor; - } - - /** - * Build version number - * @return Current build version number. - */ - public int getBuild() { - return build; - } - - /** - * Retrieve the development stage. - * @return Development stage, or NULL if this is a release. - */ - public String getDevelopmentStage() { - return development; - } - - /** - * Retrieve the snapshot version, or NULL if this is a release. - * @return The snapshot version. - */ - public SnapshotVersion getSnapshot() { - return snapshot; - } - - /** - * Determine if this version is a snapshot. - * @return The snapshot version. - */ - public boolean isSnapshot() { - return snapshot != null; - } - - public boolean atOrAbove() { - if (atLeast == null) { - atLeast = MinecraftVersion.atOrAbove(this); - } - - return atLeast; - } - - /** - * Retrieve the version String (major.minor.build) only. - * @return A normal version string. - */ - public String getVersion() { - if (getDevelopmentStage() == null) - return String.format("%s.%s.%s", getMajor(), getMinor(), getBuild()); - else - return String.format("%s.%s.%s-%s%s", getMajor(), getMinor(), getBuild(), - getDevelopmentStage(), isSnapshot() ? snapshot : ""); - } - - @Override - public int compareTo(MinecraftVersion o) { - if (o == null) - return 1; - - return ComparisonChain.start(). - compare(getMajor(), o.getMajor()). - compare(getMinor(), o.getMinor()). - compare(getBuild(), o.getBuild()). - // No development String means it's a release - compare(getDevelopmentStage(), o.getDevelopmentStage(), Ordering.natural().nullsLast()). - compare(getSnapshot(), o.getSnapshot(), Ordering.natural().nullsFirst()). - result(); - } - - public boolean isAtLeast(MinecraftVersion other) { - if (other == null) - return false; - - return compareTo(other) >= 0; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) - return false; - if (obj == this) - return true; - - if (obj instanceof MinecraftVersion) { - MinecraftVersion other = (MinecraftVersion) obj; - - return getMajor() == other.getMajor() && - getMinor() == other.getMinor() && - getBuild() == other.getBuild() && - Objects.equal(getDevelopmentStage(), other.getDevelopmentStage()); - } - - return false; - } - - @Override - public int hashCode() { - return Objects.hashCode(getMajor(), getMinor(), getBuild()); - } - - @Override - public String toString() { - // Convert to a String that we can parse back again - return String.format("(MC: %s)", getVersion()); - } - /** * Extract the Minecraft version from CraftBukkit itself. + * * @param text - the server version in text form. * @return The underlying MC version. * @throws IllegalStateException If we could not parse the version string. */ public static String extractVersion(String text) { Matcher version = VERSION_PATTERN.matcher(text); - + if (version.matches() && version.group(1) != null) { return version.group(1); } else { throw new IllegalStateException("Cannot parse version String '" + text + "'"); } } - + /** * Parse the given server version into a Minecraft version. + * * @param serverVersion - the server version. * @return The resulting Minecraft version. */ @@ -387,12 +251,6 @@ public class MinecraftVersion implements Comparable, Serializa return new MinecraftVersion(extractVersion(serverVersion)); } - private static MinecraftVersion currentVersion; - - public static void setCurrentVersion(MinecraftVersion version) { - currentVersion = version; - } - public static MinecraftVersion getCurrentVersion() { if (currentVersion == null) { currentVersion = fromServerVersion(Bukkit.getVersion()); @@ -401,7 +259,163 @@ public class MinecraftVersion implements Comparable, Serializa return currentVersion; } + public static void setCurrentVersion(MinecraftVersion version) { + currentVersion = version; + } + public static boolean atOrAbove(MinecraftVersion version) { return getCurrentVersion().isAtLeast(version); } + + private int[] parseVersion(String version) { + String[] elements = version.split("\\."); + int[] numbers = new int[3]; + + // Make sure it's even a valid version + if (elements.length < 1) { + throw new IllegalStateException("Corrupt MC version: " + version); + } + + // 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()); + } + return numbers; + } + + /** + * Major version number + * + * @return Current major version number. + */ + public int getMajor() { + return this.major; + } + + /** + * Minor version number + * + * @return Current minor version number. + */ + public int getMinor() { + return this.minor; + } + + /** + * Build version number + * + * @return Current build version number. + */ + public int getBuild() { + return this.build; + } + + /** + * Retrieve the development stage. + * + * @return Development stage, or NULL if this is a release. + */ + public String getDevelopmentStage() { + return this.development; + } + + /** + * Retrieve the snapshot version, or NULL if this is a release. + * + * @return The snapshot version. + */ + public SnapshotVersion getSnapshot() { + return this.snapshot; + } + + /** + * Determine if this version is a snapshot. + * + * @return The snapshot version. + */ + public boolean isSnapshot() { + return this.snapshot != null; + } + + /** + * Checks if this version is at or above the current version the server is running. + * + * @return true if this version is equal or newer than the server version, false otherwise. + */ + public boolean atOrAbove() { + if (this.atCurrentOrAbove == null) { + this.atCurrentOrAbove = MinecraftVersion.atOrAbove(this); + } + + return this.atCurrentOrAbove; + } + + /** + * Retrieve the version String (major.minor.build) only. + * + * @return A normal version string. + */ + public String getVersion() { + if (this.getDevelopmentStage() == null) { + return String.format("%s.%s.%s", this.getMajor(), this.getMinor(), this.getBuild()); + } else { + return String.format("%s.%s.%s-%s%s", this.getMajor(), this.getMinor(), this.getBuild(), + this.getDevelopmentStage(), this.isSnapshot() ? this.snapshot : ""); + } + } + + @Override + public int compareTo(MinecraftVersion o) { + if (o == null) { + return 1; + } + + return ComparisonChain.start() + .compare(this.getMajor(), o.getMajor()) + .compare(this.getMinor(), o.getMinor()) + .compare(this.getBuild(), o.getBuild()) + .compare(this.getDevelopmentStage(), o.getDevelopmentStage(), Ordering.natural().nullsLast()) + .compare(this.getSnapshot(), o.getSnapshot(), Ordering.natural().nullsFirst()) + .result(); + } + + public boolean isAtLeast(MinecraftVersion other) { + if (other == null) { + return false; + } + + return this.compareTo(other) >= 0; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + + if (obj instanceof MinecraftVersion) { + MinecraftVersion other = (MinecraftVersion) obj; + + return this.getMajor() == other.getMajor() && + this.getMinor() == other.getMinor() && + this.getBuild() == other.getBuild() && + Objects.equals(this.getDevelopmentStage(), other.getDevelopmentStage()); + } + + return false; + } + + @Override + public int hashCode() { + return Objects.hash(this.getMajor(), this.getMinor(), this.getBuild()); + } + + @Override + public String toString() { + // Convert to a String that we can parse back again + return String.format("(MC: %s)", this.getVersion()); + } } diff --git a/src/main/java/com/comphenix/protocol/utility/NettyVersion.java b/src/main/java/com/comphenix/protocol/utility/NettyVersion.java deleted file mode 100644 index 59b271fe..00000000 --- a/src/main/java/com/comphenix/protocol/utility/NettyVersion.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.comphenix.protocol.utility; - -import com.comphenix.protocol.ProtocolLibrary; -import io.netty.util.Version; - -import java.util.Map; - -public class NettyVersion { - private final static String NETTY_COMMON_ID = "netty-common"; - private final static String NETTY_ALL_ID = "netty-all"; - private static NettyVersion version; - - public static NettyVersion getVersion() { - if(version == null) { - version = detectVersion(); - } - return version; - } - - private static NettyVersion detectVersion() { - Map nettyArtifacts = Version.identify(); - Version version = nettyArtifacts.get(NETTY_COMMON_ID); - if(version == null) { - version = nettyArtifacts.get(NETTY_ALL_ID); - } - if(version != null) { - return new NettyVersion(version.artifactVersion()); - } - return new NettyVersion(null); - } - - private boolean valid = false; - private int major, minor, revision; - - public NettyVersion(String s) { - if(s == null) { - return; - } - String[] split = s.split( "\\."); - try { - this.major = Integer.parseInt(split[0]); - this.minor = Integer.parseInt(split[1]); - this.revision = Integer.parseInt(split[2]); - this.valid = true; - } catch (Throwable t) { - ProtocolLibrary.getPlugin().getLogger().warning("Could not detect netty version: '" + s + "'"); - } - } - - @Override - public String toString() { - if(!valid) { - return "(invalid)"; - } - return major + "." + minor + "." + revision; - } - - @Override - public boolean equals(Object obj) { - if(!(obj instanceof NettyVersion)) { - return false; - } - NettyVersion v = (NettyVersion) obj; - return v.major == major && v.minor == minor && v.revision == revision; - } - - public int getMajor() { - return major; - } - - public int getMinor() { - return minor; - } - - public int getRevision() { - return revision; - } - - public boolean isValid() { - return this.valid; - } - - public boolean isGreaterThan(int major, int minor, int rev) { - return this.major > major || this.minor > minor || this.revision > rev; - } - -} diff --git a/src/main/java/com/comphenix/protocol/utility/ObjectReconstructor.java b/src/main/java/com/comphenix/protocol/utility/ObjectReconstructor.java deleted file mode 100644 index 41d57c84..00000000 --- a/src/main/java/com/comphenix/protocol/utility/ObjectReconstructor.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.comphenix.protocol.utility; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; - -/** - * This class can be used to reconstruct objects. - * - * Note that it is limited to classes where both the order and number of member variables matches the order and number - * of arguments for the first constructor. This means that this class is mostly useful for classes generated by lambdas. - * - * @param The type of the object to reconstruct. - * @author Pim - */ -public class ObjectReconstructor { - - private final Class clz; - private final Field[] fields; - private final Constructor ctor; - - public ObjectReconstructor(final Class clz) { - this.clz = clz; - this.fields = clz.getDeclaredFields(); - for (Field field : fields) - field.setAccessible(true); - this.ctor = clz.getDeclaredConstructors()[0]; - this.ctor.setAccessible(true); - } - - /** - * Gets the values of all member variables of the provided instance. - * @param instance The instance for which to get all the member variables. - * @return The values of the member variables from the instance. - */ - public Object[] getValues(final Object instance) { - final Object[] values = new Object[fields.length]; - for (int idx = 0; idx < fields.length; ++idx) - try { - values[idx] = fields[idx].get(instance); - } catch (IllegalAccessException e) { - throw new RuntimeException("Failed to access field: " + fields[idx].getName() + - " for class: " + clz.getName(), e); - } - return values; - } - - /** - * Gets the fields in the class. - * @return The fields. - */ - public Field[] getFields() { - return fields; - } - - /** - * Creates a new instance of the class using the new values. - * @param values The new values for the member variables of the class. - * @return The new instance. - */ - public T reconstruct(final Object[] values) { - if (values.length != fields.length) - throw new RuntimeException("Mismatched number of arguments for class: " + clz.getName()); - - try { - return (T) ctor.newInstance(values); - } catch (InstantiationException e) { - throw new RuntimeException("Failed to reconstruct object of type: " + clz.getName(), e); - } catch (IllegalAccessException e) { - throw new RuntimeException("Failed to access constructor of type: " + clz.getName(), e); - } catch (InvocationTargetException e) { - throw new RuntimeException("Failed to invoke constructor of type: " + clz.getName(), e); - } - } -} diff --git a/src/main/java/com/comphenix/protocol/utility/RemappedClassSource.java b/src/main/java/com/comphenix/protocol/utility/RemappedClassSource.java deleted file mode 100644 index 7a2fb91e..00000000 --- a/src/main/java/com/comphenix/protocol/utility/RemappedClassSource.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.comphenix.protocol.utility; - -// Thanks to Bergerkiller for his excellent hack. :D - -// Copyright (C) 2013 bergerkiller -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the -// Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import java.lang.reflect.Method; - -import org.bukkit.Bukkit; -import org.bukkit.Server; - -import com.comphenix.protocol.reflect.FieldUtils; -import com.comphenix.protocol.reflect.MethodUtils; -import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException.Reason; - -class RemappedClassSource extends ClassSource { - private Object classRemapper; - private Method mapType; - private ClassLoader loader; - - /** - * Construct a new remapped class source using the default class loader. - */ - public RemappedClassSource() { - this(RemappedClassSource.class.getClassLoader()); - } - - /** - * Construct a new renampped class source with the provided class loader. - * @param loader - the class loader. - */ - public RemappedClassSource(ClassLoader loader) { - this.loader = loader; - } - - /** - * Attempt to load the MCPC remapper. - * @return TRUE if we succeeded, FALSE otherwise. - * @throws RemapperUnavailableException If the remapper is not present. - */ - public RemappedClassSource initialize() { - try { - Server server = Bukkit.getServer(); - if (server == null) { - throw new IllegalStateException("Bukkit not initialized."); - } - - String version = server.getVersion(); - if (!version.contains("MCPC") && !version.contains("Cauldron")) { - throw new RemapperUnavailableException(Reason.MCPC_NOT_PRESENT); - } - - // Obtain the Class remapper used by MCPC+/Cauldron - this.classRemapper = FieldUtils.readField(getClass().getClassLoader(), "remapper", true); - - if (this.classRemapper == null) { - throw new RemapperUnavailableException(Reason.REMAPPER_DISABLED); - } - - // Initialize some fields and methods used by the Jar Remapper - Class renamerClazz = classRemapper.getClass(); - - this.mapType = MethodUtils.getAccessibleMethod(renamerClazz, "map", - new Class[] { String.class }); - - return this; - - } catch (RemapperUnavailableException e) { - throw e; - } catch (Exception e) { - // Damn it - throw new RuntimeException("Cannot access MCPC remapper.", e); - } - } - - @Override - public Class loadClass(String canonicalName) throws ClassNotFoundException { - final String remapped = getClassName(canonicalName); - - try { - return loader.loadClass(remapped); - } catch (ClassNotFoundException e) { - throw new ClassNotFoundException("Cannot find " + canonicalName + "(Remapped: " + remapped + ")"); - } - } - - /** - * Retrieve the obfuscated class name given an unobfuscated canonical class name. - * @param path - the canonical class name. - * @return The obfuscated class name. - */ - public String getClassName(String path) { - try { - String remapped = (String) mapType.invoke(classRemapper, path.replace('.', '/')); - return remapped.replace('/', '.'); - } catch (Exception e) { - throw new RuntimeException("Cannot remap class name.", e); - } - } - - public static class RemapperUnavailableException extends RuntimeException { - private static final long serialVersionUID = 1L; - - public enum Reason { - MCPC_NOT_PRESENT("The server is not running MCPC+/Cauldron"), - REMAPPER_DISABLED("Running an MCPC+/Cauldron server but the remapper is unavailable. Please turn it on!"); - - private final String message; - - private Reason(String message) { - this.message = message; - } - - /** - * Retrieve a human-readable version of this reason. - * @return The human-readable verison. - */ - public String getMessage() { - return message; - } - } - - private final Reason reason; - - public RemapperUnavailableException(Reason reason) { - super(reason.getMessage()); - this.reason = reason; - } - - /** - * Retrieve the reasont he remapper is unavailable. - * @return The reason. - */ - public Reason getReason() { - return reason; - } - } -} diff --git a/src/main/java/com/comphenix/protocol/utility/SnapshotVersion.java b/src/main/java/com/comphenix/protocol/utility/SnapshotVersion.java index 9644165e..35d5f37c 100644 --- a/src/main/java/com/comphenix/protocol/utility/SnapshotVersion.java +++ b/src/main/java/com/comphenix/protocol/utility/SnapshotVersion.java @@ -1,35 +1,34 @@ package com.comphenix.protocol.utility; +import com.google.common.collect.ComparisonChain; import java.io.Serializable; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.google.common.base.Objects; -import com.google.common.collect.ComparisonChain; - /** * Used to parse a snapshot version. + * * @author Kristian */ public class SnapshotVersion implements Comparable, Serializable { - // Increment when the class changes - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2778655372579322310L; private static final Pattern SNAPSHOT_PATTERN = Pattern.compile("(\\d{2}w\\d{2})([a-z])"); private final Date snapshotDate; private final int snapshotWeekVersion; private transient String rawString; - + public SnapshotVersion(String version) { Matcher matcher = SNAPSHOT_PATTERN.matcher(version.trim()); - + if (matcher.matches()) { try { this.snapshotDate = getDateFormat().parse(matcher.group(1)); @@ -42,11 +41,12 @@ public class SnapshotVersion implements Comparable, Serializabl throw new IllegalArgumentException("Cannot parse " + version + " as a snapshot version."); } } - + /** * Retrieve the snapshot date parser. *

    * We have to create a new instance of SimpleDateFormat every time as it is not thread safe. + * * @return The date formatter. */ private static SimpleDateFormat getDateFormat() { @@ -54,70 +54,77 @@ public class SnapshotVersion implements Comparable, Serializabl format.setLenient(false); return format; } - + /** * Retrieve the snapshot version within a week, starting at zero. + * * @return The weekly version */ public int getSnapshotWeekVersion() { - return snapshotWeekVersion; + return this.snapshotWeekVersion; } - + /** * Retrieve the week this snapshot was released. + * * @return The week. */ public Date getSnapshotDate() { - return snapshotDate; + return this.snapshotDate; } - + /** * Retrieve the raw snapshot string (yy'w'ww[a-z]). + * * @return The snapshot string. */ public String getSnapshotString() { - if (rawString == null) { + if (this.rawString == null) { // It's essential that we use the same locale Calendar current = Calendar.getInstance(Locale.US); - current.setTime(snapshotDate); - rawString = String.format("%02dw%02d%s", - current.get(Calendar.YEAR) % 100, - current.get(Calendar.WEEK_OF_YEAR), - (char) ('a' + snapshotWeekVersion)); + current.setTime(this.snapshotDate); + this.rawString = String.format("%02dw%02d%s", + current.get(Calendar.YEAR) % 100, + current.get(Calendar.WEEK_OF_YEAR), + (char) ('a' + this.snapshotWeekVersion)); } - return rawString; + return this.rawString; } @Override public int compareTo(SnapshotVersion o) { - if (o == null) + if (o == null) { return 1; - - return ComparisonChain.start(). - compare(snapshotDate, o.getSnapshotDate()). - compare(snapshotWeekVersion, o.getSnapshotWeekVersion()). - result(); + } + + return ComparisonChain.start() + .compare(this.snapshotDate, o.getSnapshotDate()) + .compare(this.snapshotWeekVersion, o.getSnapshotWeekVersion()) + .result(); } - + @Override public boolean equals(Object obj) { - if (obj == this) + if (obj == this) { return true; + } + if (obj instanceof SnapshotVersion) { SnapshotVersion other = (SnapshotVersion) obj; - return Objects.equal(snapshotDate, other.getSnapshotDate()) && - snapshotWeekVersion == other.getSnapshotWeekVersion(); + return Objects.equals(this.snapshotDate, other.getSnapshotDate()) + && this.snapshotWeekVersion == other.getSnapshotWeekVersion(); } + return false; } - + @Override public int hashCode() { - return Objects.hashCode(snapshotDate, snapshotWeekVersion); + return Objects.hash(this.snapshotDate, this.snapshotWeekVersion); } - + @Override public String toString() { - return getSnapshotString(); + return this.getSnapshotString(); } } diff --git a/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java b/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java index 43444440..49ca3c7f 100644 --- a/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java +++ b/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java @@ -1,52 +1,44 @@ package com.comphenix.protocol.utility; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInput; -import java.io.DataInputStream; -import java.io.DataOutput; -import java.io.DataOutputStream; -import java.io.IOException; - -import javax.annotation.Nonnull; - -import org.apache.commons.lang.Validate; -import org.bukkit.inventory.ItemStack; -import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; - import com.comphenix.protocol.injector.netty.NettyByteBufAdapter; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.MethodAccessor; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.comphenix.protocol.wrappers.nbt.NbtType; -import com.google.common.base.Preconditions; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.util.ReferenceCountUtil; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Base64; +import org.bukkit.inventory.ItemStack; /** * Utility methods for reading and writing Minecraft objects to streams. - * + * * @author Kristian */ public class StreamSerializer { + private static final StreamSerializer DEFAULT = new StreamSerializer(); - + // Cached methods private static MethodAccessor READ_ITEM_METHOD; private static MethodAccessor WRITE_ITEM_METHOD; private static MethodAccessor READ_NBT_METHOD; private static MethodAccessor WRITE_NBT_METHOD; - + private static MethodAccessor READ_STRING_METHOD; private static MethodAccessor WRITE_STRING_METHOD; - + /** * Retrieve a default stream serializer. + * * @return A serializer. */ public static StreamSerializer getDefault() { @@ -55,247 +47,145 @@ public class StreamSerializer { /** * Write a variable integer to an output stream. + * * @param destination - the destination. - * @param value - the value to write. + * @param value - the value to write. * @throws IOException The destination stream threw an exception. */ - public void serializeVarInt(@Nonnull DataOutputStream destination, int value) throws IOException { - Preconditions.checkNotNull(destination, "source cannot be NULL"); - - while ((value & 0xFFFFFF80) != 0) { - destination.writeByte(value & 0x7F | 0x80); - value >>>= 7; + public void serializeVarInt(DataOutputStream destination, int value) throws IOException { + while (true) { + if ((value & ~0x7F) == 0) { + destination.writeByte(value); + break; + } else { + destination.writeByte((value & 0x7F) | 0x80); + value >>>= 7; + } } - destination.writeByte(value); } /** * Read a variable integer from an input stream. + * * @param source - the source. * @return The integer. * @throws IOException The source stream threw an exception. */ - public int deserializeVarInt(@Nonnull DataInputStream source) throws IOException { - Preconditions.checkNotNull(source, "source cannot be NULL"); - + public int deserializeVarInt(DataInputStream source) throws IOException { int result = 0; - int length = 0; - byte currentByte; - do { - currentByte = source.readByte(); - result |= (currentByte & 0x7F) << length++ * 7; - if (length > 5) - throw new RuntimeException("VarInt too big"); - } while ((currentByte & 0x80) == 0x80); - - return result; + for (byte j = 0; j < 5; j++) { + int nextByte = source.readByte(); + result |= (nextByte & 0x7F) << j * 7; + if ((nextByte & 0x80) != 128) { + return result; + } + } + throw new RuntimeException("VarInt is too big"); } /** * Write or serialize a NBT compound to the given output stream. *

    * Note: An NBT compound can be written to a stream even if it's NULL. - * - * @param output - the target output stream. + * + * @param output - the target output stream. * @param compound - the NBT compound to be serialized, or NULL to represent nothing. - * @throws IOException If the operation fails due to reflection problems. */ - public void serializeCompound(@Nonnull DataOutputStream output, NbtCompound compound) throws IOException { - if (output == null) - throw new IllegalArgumentException("Output stream cannot be NULL."); - + public void serializeCompound(DataOutputStream output, NbtCompound compound) { + if (WRITE_NBT_METHOD == null) { + WRITE_NBT_METHOD = Accessors.getMethodAccessor(FuzzyReflection + .fromClass(MinecraftReflection.getPacketDataSerializerClass(), true) + .getMethodByParameters("writeNbtCompound", MinecraftReflection.getNBTCompoundClass())); + } + + ByteBuf buf = NettyByteBufAdapter.packetWriter(output); + buf.writeByte(NbtType.TAG_COMPOUND.getRawID()); + // Get the NMS version of the compound Object handle = compound != null ? NbtFactory.fromBase(compound).getHandle() : null; - - if (MinecraftReflection.isUsingNetty()) { - if (WRITE_NBT_METHOD == null) { - WRITE_NBT_METHOD = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true). - getMethodByParameters("writeNbtCompound", /* a */ - MinecraftReflection.getNBTCompoundClass()) - ); - } - - ByteBuf buf = NettyByteBufAdapter.packetWriter(output); - buf.writeByte(NbtType.TAG_COMPOUND.getRawID()); - - WRITE_NBT_METHOD.invoke(buf, handle); - } else { - if (WRITE_NBT_METHOD == null) { - WRITE_NBT_METHOD = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true).getMethod( - FuzzyMethodContract.newBuilder(). - parameterCount(2). - parameterDerivedOf(MinecraftReflection.getNBTBaseClass(), 0). - parameterDerivedOf(DataOutput.class, 1). - returnTypeVoid(). - build()) - ); - } - - WRITE_NBT_METHOD.invoke(null, handle, output); - } + WRITE_NBT_METHOD.invoke(buf, handle); } /** * Read or deserialize an NBT compound from a input stream. + * * @param input - the target input stream. * @return The resulting compound, or NULL. - * @throws IOException If the operation failed due to reflection or corrupt data. */ - public NbtCompound deserializeCompound(@Nonnull DataInputStream input) throws IOException { - if (input == null) - throw new IllegalArgumentException("Input stream cannot be NULL."); - Object nmsCompound = null; - - // Invoke the correct method - if (MinecraftReflection.isUsingNetty()) { - if (READ_NBT_METHOD == null) { - READ_NBT_METHOD = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true). - getMethodByParameters("readNbtCompound", /* h */ - MinecraftReflection.getNBTCompoundClass(), new Class[0]) - ); - } - - ByteBuf buf = NettyByteBufAdapter.packetReader(input); - nmsCompound = READ_NBT_METHOD.invoke(buf); - } else { - if (READ_NBT_METHOD == null) { - READ_NBT_METHOD = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( - FuzzyMethodContract.newBuilder(). - parameterCount(1). - parameterDerivedOf(DataInput.class). - returnDerivedOf(MinecraftReflection.getNBTBaseClass()). - build()) - ); - } - - try { - nmsCompound = READ_NBT_METHOD.invoke(null, input); - } catch (Exception e) { - throw new IOException("Cannot read item stack.", e); - } + public NbtCompound deserializeCompound(DataInputStream input) { + if (READ_NBT_METHOD == null) { + READ_NBT_METHOD = Accessors.getMethodAccessor(FuzzyReflection + .fromClass(MinecraftReflection.getPacketDataSerializerClass(), true) + .getMethodByReturnTypeAndParameters("readNbtCompound", MinecraftReflection.getNBTCompoundClass())); } - // Convert back to an NBT Compound - if (nmsCompound != null) - return NbtFactory.fromNMSCompound(nmsCompound); - else - return null; + ByteBuf buf = NettyByteBufAdapter.packetReader(input); + + // deserialize and wrap if needed + Object nmsCompound = READ_NBT_METHOD.invoke(buf); + return nmsCompound == null ? null : NbtFactory.fromNMSCompound(nmsCompound); } /** * Serialize a string using the standard Minecraft UTF-16 encoding. *

    * Note that strings cannot exceed 32767 characters, regardless if maximum lenght. + * * @param output - the output stream. - * @param text - the string to serialize. - * @throws IOException If the data in the string cannot be written. + * @param text - the string to serialize. */ - public void serializeString(@Nonnull DataOutputStream output, String text) throws IOException { - if (output == null) - throw new IllegalArgumentException("output stream cannot be NULL."); - if (text == null) - throw new IllegalArgumentException("text cannot be NULL."); - - if (MinecraftReflection.isUsingNetty()) { - if (WRITE_STRING_METHOD == null) { - WRITE_STRING_METHOD = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true). - getMethodByParameters("writeString", /* a */ - String.class) - ); - } - - ByteBuf buf = NettyByteBufAdapter.packetWriter(output); - WRITE_STRING_METHOD.invoke(buf, text); - } else { - if (WRITE_STRING_METHOD == null) { - WRITE_STRING_METHOD = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( - FuzzyMethodContract.newBuilder(). - parameterCount(2). - parameterExactType(String.class, 0). - parameterDerivedOf(DataOutput.class, 1). - returnTypeVoid(). - build()) - ); - } - - WRITE_STRING_METHOD.invoke(null, text, output); + public void serializeString(DataOutputStream output, String text) { + if (WRITE_STRING_METHOD == null) { + WRITE_STRING_METHOD = Accessors.getMethodAccessor(FuzzyReflection + .fromClass(MinecraftReflection.getPacketDataSerializerClass(), true) + .getMethodByParameters("writeString", String.class)); } + + ByteBuf buf = NettyByteBufAdapter.packetWriter(output); + WRITE_STRING_METHOD.invoke(buf, text); } - + /** * Deserialize a string using the standard Minecraft UTF-16 encoding. *

    * Note that strings cannot exceed 32767 characters, regardless if maximum length. - * @param input - the input stream. + * + * @param input - the input stream. * @param maximumLength - the maximum length of the string. * @return The deserialized string. - * @throws IOException If deserializing fails */ - public String deserializeString(@Nonnull DataInputStream input, int maximumLength) throws IOException { - if (input == null) - throw new IllegalArgumentException("Input stream cannot be NULL."); - if (maximumLength > 32767) - throw new IllegalArgumentException("Maximum length cannot exceed 32767 characters."); - if (maximumLength < 0) - throw new IllegalArgumentException("Maximum length cannot be negative."); - - if (MinecraftReflection.isUsingNetty()) { - if (READ_STRING_METHOD == null) { - READ_STRING_METHOD = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true). - getMethodByParameters("readString", /* c */ - String.class, new Class[] { int.class }) - ); - } - - ByteBuf buf = NettyByteBufAdapter.packetReader(input); - return (String) READ_STRING_METHOD.invoke(buf, maximumLength); - } else { - if (READ_STRING_METHOD == null) { - READ_STRING_METHOD = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( - FuzzyMethodContract.newBuilder(). - parameterCount(2). - parameterDerivedOf(DataInput.class, 0). - parameterExactType(int.class, 1). - returnTypeExact(String.class). - build()) - ); - } - - return (String) READ_STRING_METHOD.invoke(null, input, maximumLength); + public String deserializeString(DataInputStream input, int maximumLength) { + if (READ_STRING_METHOD == null) { + READ_STRING_METHOD = Accessors.getMethodAccessor(FuzzyReflection + .fromClass(MinecraftReflection.getPacketDataSerializerClass(), true) + .getMethodByReturnTypeAndParameters("readString", String.class, int.class)); } + + ByteBuf buf = NettyByteBufAdapter.packetReader(input); + return (String) READ_STRING_METHOD.invoke(buf, maximumLength); } /** * Serialize an item stack as a base-64 encoded string. *

    * Note: An ItemStack can be written to the serialized text even if it's NULL. - * + * * @param stack - the item stack to serialize, or NULL to represent air/nothing. * @return A base-64 representation of the given item stack. * @throws IOException If the operation fails due to reflection problems. */ public String serializeItemStack(ItemStack stack) throws IOException { - return Base64Coder.encodeLines(serializeItemStackToByteArray(stack)); + return Base64.getEncoder().encodeToString(this.serializeItemStackToByteArray(stack)); } /** * Deserialize an item stack from a base-64 encoded string. + * * @param input - base-64 encoded string. * @return A deserialized item stack, or NULL if the serialized ItemStack was also NULL. - * @throws IOException If the operation failed due to reflection or corrupt data. */ - public ItemStack deserializeItemStack(String input) throws IOException { - Validate.notNull(input, "input cannot be null!"); - - return deserializeItemStackFromByteArray(Base64Coder.decodeLines(input)); + public ItemStack deserializeItemStack(String input) { + return this.deserializeItemStackFromByteArray(Base64.getDecoder().decode(input)); } /** @@ -308,157 +198,80 @@ public class StreamSerializer { * @throws IOException If the operation fails due to reflection problems. */ public byte[] serializeItemStackToByteArray(ItemStack stack) throws IOException { - - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - DataOutputStream output = new DataOutputStream(outputStream); - - serializeItemStack(output, stack); - - return outputStream.toByteArray(); + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); DataOutputStream data = new DataOutputStream(out)) { + this.serializeItemStack(data, stack); + return out.toByteArray(); + } } /** * Deserialize an item stack from a byte array. + * * @param input - serialized item. * @return A deserialized item stack, or NULL if the serialized ItemStack was also NULL. - * @throws IOException If the operation failed due to reflection or corrupt data. */ - public ItemStack deserializeItemStackFromByteArray(byte[] input) throws IOException { - Validate.notNull(input, "input cannot be null!"); - - Object nmsItem; - - if (MinecraftReflection.isUsingNetty()) { - ByteBuf buf = Unpooled.copiedBuffer(input); - Object serializer = MinecraftReflection.getPacketDataSerializer(buf); - - if (READ_ITEM_METHOD == null) { - READ_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true). - getMethodByParameters("readItemStack", // i(ItemStack) - MinecraftReflection.getItemStackClass(), new Class[0])); - } - - nmsItem = READ_ITEM_METHOD.invoke(serializer); - } else { - if (READ_ITEM_METHOD == null) { - READ_ITEM_METHOD = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( - FuzzyMethodContract.newBuilder(). - parameterCount(1). - parameterDerivedOf(DataInput.class). - returnDerivedOf(MinecraftReflection.getItemStackClass()). - build()) - ); - } - - ByteArrayInputStream byteStream = new ByteArrayInputStream(input); - DataInputStream inputStream = new DataInputStream(byteStream); - - nmsItem = READ_ITEM_METHOD.invoke(null, inputStream); + public ItemStack deserializeItemStackFromByteArray(byte[] input) { + if (READ_ITEM_METHOD == null) { + READ_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection + .fromClass(MinecraftReflection.getPacketDataSerializerClass(), true) + .getMethodByReturnTypeAndParameters("readItemStack", MinecraftReflection.getItemStackClass())); } - return nmsItem != null ? MinecraftReflection.getBukkitItemStack(nmsItem) : null; + ByteBuf buf = Unpooled.wrappedBuffer(input); + Object serializer = MinecraftReflection.getPacketDataSerializer(buf); + + try { + // unwrap the item + Object nmsItem = READ_ITEM_METHOD.invoke(serializer); + return nmsItem != null ? MinecraftReflection.getBukkitItemStack(nmsItem) : null; + } finally { + ReferenceCountUtil.safeRelease(buf); + } } /** * Write or serialize an item stack to the given output stream. *

    - * To supply a byte array, wrap it in a {@link java.io.ByteArrayOutputStream ByteArrayOutputStream} - * and {@link java.io.DataOutputStream DataOutputStream}. + * To supply a byte array, wrap it in a {@link java.io.ByteArrayOutputStream ByteArrayOutputStream} and {@link + * java.io.DataOutputStream DataOutputStream}. *

    * Note: An ItemStack can be written to a stream even if it's NULL. - * + * * @param output - the target output stream. - * @param stack - the item stack that will be written, or NULL to represent air/nothing. + * @param stack - the item stack that will be written, or NULL to represent air/nothing. * @throws IOException If the operation fails due to reflection problems. */ public void serializeItemStack(DataOutputStream output, ItemStack stack) throws IOException { - Validate.notNull(output, "output cannot be null!"); - - // Get the NMS version of the ItemStack - Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack); - - if (MinecraftReflection.isUsingNetty()) { - if (WRITE_ITEM_METHOD == null) { - WRITE_ITEM_METHOD = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true). - getMethodByParameters("writeStack", /* a */ - MinecraftReflection.getItemStackClass()) - ); - } - - ByteBuf buf = Unpooled.buffer(); - Object serializer = MinecraftReflection.getPacketDataSerializer(buf); - - WRITE_ITEM_METHOD.invoke(serializer, nmsItem); - - output.write(buf.array()); - } else { - if (WRITE_ITEM_METHOD == null) - WRITE_ITEM_METHOD = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( - FuzzyMethodContract.newBuilder(). - parameterCount(2). - parameterDerivedOf(MinecraftReflection.getItemStackClass(), 0). - parameterDerivedOf(DataOutput.class, 1). - build()) - ); - - WRITE_ITEM_METHOD.invoke(null, nmsItem, output); + if (WRITE_ITEM_METHOD == null) { + WRITE_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection + .fromClass(MinecraftReflection.getPacketDataSerializerClass(), true) + .getMethodByParameters("writeStack", MinecraftReflection.getItemStackClass())); } + + ByteBuf buf = Unpooled.buffer(); + Object serializer = MinecraftReflection.getPacketDataSerializer(buf); + + // Get the NMS version of the ItemStack and write it into the buffer + Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack); + WRITE_ITEM_METHOD.invoke(serializer, nmsItem); + + // write the serialized content to the stream + output.write(this.getBytesAndRelease(buf)); } - /** - * Read or deserialize an item stack from an underlying input stream. - *

    - * To supply a byte array, wrap it in a {@link java.io.ByteArrayInputStream ByteArrayInputStream} - * and {@link java.io.DataInputStream DataInputStream}. - * - * @param input - the target input stream. - * @return The resulting item stack, or NULL if the serialized item stack was NULL. - * @throws IOException If the operation failed due to reflection or corrupt data. - * @deprecated This is a pretty hacky solution for backwards compatibility. See {@link #deserializeItemStack(DataInputStream)} - */ - @Deprecated - public ItemStack deserializeItemStack(DataInputStream input) throws IOException { - Validate.notNull(input, "input cannot be null!"); - Object nmsItem; - - if (MinecraftReflection.isUsingNetty()) { - if (READ_ITEM_METHOD == null) { - READ_ITEM_METHOD = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true). - getMethodByParameters("readItemStack", /* i */ - MinecraftReflection.getItemStackClass(), new Class[0]) - ); + public byte[] getBytesAndRelease(ByteBuf buf) { + try { + if (buf.hasArray()) { + // heap buffer, we can access the array directly + return buf.array(); + } else { + // direct buffer, we need to copy the bytes into an array + byte[] bytes = new byte[buf.readableBytes()]; + buf.readBytes(bytes); + return bytes; } - - byte[] bytes = new byte[8192]; - input.read(bytes); - - ByteBuf buf = Unpooled.copiedBuffer(bytes); - Object serializer = MinecraftReflection.getPacketDataSerializer(buf); - - nmsItem = READ_ITEM_METHOD.invoke(serializer); - } else { - if (READ_ITEM_METHOD == null) { - READ_ITEM_METHOD = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( - FuzzyMethodContract.newBuilder(). - parameterCount(1). - parameterDerivedOf(DataInput.class). - returnDerivedOf(MinecraftReflection.getItemStackClass()). - build()) - ); - } - - nmsItem = READ_ITEM_METHOD.invoke(null, input); + } finally { + ReferenceCountUtil.safeRelease(buf); } - - // Convert back to a Bukkit item stack - if (nmsItem != null) - return MinecraftReflection.getBukkitItemStack(nmsItem); - else - return null; } } diff --git a/src/main/java/com/comphenix/protocol/utility/Util.java b/src/main/java/com/comphenix/protocol/utility/Util.java index 703d204f..8bde8856 100644 --- a/src/main/java/com/comphenix/protocol/utility/Util.java +++ b/src/main/java/com/comphenix/protocol/utility/Util.java @@ -14,32 +14,14 @@ */ package com.comphenix.protocol.utility; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - /** * General utility class + * * @author dmulloy2 */ public final class Util { - private static final boolean spigot = classExists("org.spigotmc.SpigotConfig"); - - private Util() { - } - - /** - * Converts a variable argument array into a List. - * @param elements Array to convert - * @return The list - */ - @SafeVarargs - public static List asList(E... elements) { - List list = new ArrayList<>(elements.length); - Collections.addAll(list, elements); - return list; - } + private static final boolean SPIGOT = classExists("org.spigotmc.SpigotConfig"); public static boolean classExists(String className) { try { @@ -51,12 +33,13 @@ public final class Util { } /** - * Whether or not this server is running Spigot or a Spigot fork. This works by checking - * if the SpigotConfig exists, which should be true of all forks. + * Whether this server is running Spigot or a Spigot fork. This works by checking if the SpigotConfig exists, which + * should be true of all forks. + * * @return True if it is, false if not. */ public static boolean isUsingSpigot() { - return spigot; + return SPIGOT; } /** diff --git a/src/main/java/com/comphenix/protocol/utility/ZeroBuffer.java b/src/main/java/com/comphenix/protocol/utility/ZeroBuffer.java index a813cba0..d24b468b 100644 --- a/src/main/java/com/comphenix/protocol/utility/ZeroBuffer.java +++ b/src/main/java/com/comphenix/protocol/utility/ZeroBuffer.java @@ -3,7 +3,6 @@ package com.comphenix.protocol.utility; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufProcessor; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -14,728 +13,729 @@ import java.nio.channels.ScatteringByteChannel; import java.nio.charset.Charset; public class ZeroBuffer extends ByteBuf { - @Override - public int capacity() { - return 0; - } - - @Override - public ByteBuf capacity(int i) { - return null; - } - - @Override - public int maxCapacity() { - return 0; - } - - @Override - public ByteBufAllocator alloc() { - return null; - } - - @Override - public ByteOrder order() { - return null; - } - - @Override - public ByteBuf order(ByteOrder byteOrder) { - return null; - } - - @Override - public ByteBuf unwrap() { - return null; - } - - @Override - public boolean isDirect() { - return false; - } - - @Override - public int readerIndex() { - return 0; - } - - @Override - public ByteBuf readerIndex(int i) { - return null; - } - - @Override - public int writerIndex() { - return 0; - } - - @Override - public ByteBuf writerIndex(int i) { - return null; - } - - @Override - public ByteBuf setIndex(int i, int i1) { - return null; - } - - @Override - public int readableBytes() { - return 0; - } - - @Override - public int writableBytes() { - return 0; - } - - @Override - public int maxWritableBytes() { - return 0; - } - - @Override - public boolean isReadable() { - return false; - } - - @Override - public boolean isReadable(int i) { - return false; - } - - @Override - public boolean isWritable() { - return false; - } - - @Override - public boolean isWritable(int i) { - return false; - } - - @Override - public ByteBuf clear() { - return null; - } - - @Override - public ByteBuf markReaderIndex() { - return null; - } - - @Override - public ByteBuf resetReaderIndex() { - return null; - } - - @Override - public ByteBuf markWriterIndex() { - return null; - } - - @Override - public ByteBuf resetWriterIndex() { - return null; - } - - @Override - public ByteBuf discardReadBytes() { - return null; - } - - @Override - public ByteBuf discardSomeReadBytes() { - return null; - } - - @Override - public ByteBuf ensureWritable(int i) { - return null; - } - - @Override - public int ensureWritable(int i, boolean b) { - return 0; - } - - @Override - public boolean getBoolean(int i) { - return false; - } - - @Override - public byte getByte(int i) { - return 0; - } - - @Override - public short getUnsignedByte(int i) { - return 0; - } - - @Override - public short getShort(int i) { - return 0; - } - - @Override - public int getUnsignedShort(int i) { - return 0; - } - - @Override - public int getMedium(int i) { - return 0; - } - - @Override - public int getUnsignedMedium(int i) { - return 0; - } - - @Override - public int getInt(int i) { - return 0; - } - - @Override - public long getUnsignedInt(int i) { - return 0; - } - - @Override - public long getLong(int i) { - return 0; - } - - @Override - public char getChar(int i) { - return 0; - } - - @Override - public float getFloat(int i) { - return 0; - } - - @Override - public double getDouble(int i) { - return 0; - } - - @Override - public ByteBuf getBytes(int i, ByteBuf byteBuf) { - return null; - } - - @Override - public ByteBuf getBytes(int i, ByteBuf byteBuf, int i1) { - return null; - } - - @Override - public ByteBuf getBytes(int i, ByteBuf byteBuf, int i1, int i2) { - return null; - } - - @Override - public ByteBuf getBytes(int i, byte[] bytes) { - return null; - } - - @Override - public ByteBuf getBytes(int i, byte[] bytes, int i1, int i2) { - return null; - } - - @Override - public ByteBuf getBytes(int i, ByteBuffer byteBuffer) { - return null; - } - - @Override - public ByteBuf getBytes(int i, OutputStream outputStream, int i1) throws IOException { - return null; - } - - @Override - public int getBytes(int i, GatheringByteChannel gatheringByteChannel, int i1) throws IOException { - return 0; - } - - @Override - public ByteBuf setBoolean(int i, boolean b) { - return null; - } - - @Override - public ByteBuf setByte(int i, int i1) { - return null; - } - - @Override - public ByteBuf setShort(int i, int i1) { - return null; - } - - @Override - public ByteBuf setMedium(int i, int i1) { - return null; - } - - @Override - public ByteBuf setInt(int i, int i1) { - return null; - } - - @Override - public ByteBuf setLong(int i, long l) { - return null; - } - - @Override - public ByteBuf setChar(int i, int i1) { - return null; - } - - @Override - public ByteBuf setFloat(int i, float v) { - return null; - } - - @Override - public ByteBuf setDouble(int i, double v) { - return null; - } - - @Override - public ByteBuf setBytes(int i, ByteBuf byteBuf) { - return null; - } - - @Override - public ByteBuf setBytes(int i, ByteBuf byteBuf, int i1) { - return null; - } - - @Override - public ByteBuf setBytes(int i, ByteBuf byteBuf, int i1, int i2) { - return null; - } - - @Override - public ByteBuf setBytes(int i, byte[] bytes) { - return null; - } - - @Override - public ByteBuf setBytes(int i, byte[] bytes, int i1, int i2) { - return null; - } - - @Override - public ByteBuf setBytes(int i, ByteBuffer byteBuffer) { - return null; - } - - @Override - public int setBytes(int i, InputStream inputStream, int i1) throws IOException { - return 0; - } - - @Override - public int setBytes(int i, ScatteringByteChannel scatteringByteChannel, int i1) throws IOException { - return 0; - } - - @Override - public ByteBuf setZero(int i, int i1) { - return null; - } - - @Override - public boolean readBoolean() { - return false; - } - - @Override - public byte readByte() { - return 0; - } - - @Override - public short readUnsignedByte() { - return 0; - } - - @Override - public short readShort() { - return 0; - } - - @Override - public int readUnsignedShort() { - return 0; - } - - @Override - public int readMedium() { - return 0; - } - - @Override - public int readUnsignedMedium() { - return 0; - } - - @Override - public int readInt() { - return 0; - } - - @Override - public long readUnsignedInt() { - return 0; - } - - @Override - public long readLong() { - return 0; - } - - @Override - public char readChar() { - return 0; - } - - @Override - public float readFloat() { - return 0; - } - - @Override - public double readDouble() { - return 0; - } - - @Override - public ByteBuf readBytes(int i) { - return null; - } - - @Override - public ByteBuf readSlice(int i) { - return null; - } - - @Override - public ByteBuf readBytes(ByteBuf byteBuf) { - return null; - } - - @Override - public ByteBuf readBytes(ByteBuf byteBuf, int i) { - return null; - } - - @Override - public ByteBuf readBytes(ByteBuf byteBuf, int i, int i1) { - return null; - } - - @Override - public ByteBuf readBytes(byte[] bytes) { - return null; - } - - @Override - public ByteBuf readBytes(byte[] bytes, int i, int i1) { - return null; - } - - @Override - public ByteBuf readBytes(ByteBuffer byteBuffer) { - return null; - } - - @Override - public ByteBuf readBytes(OutputStream outputStream, int i) throws IOException { - return null; - } - - @Override - public int readBytes(GatheringByteChannel gatheringByteChannel, int i) throws IOException { - return 0; - } - - @Override - public ByteBuf skipBytes(int i) { - return null; - } - - @Override - public ByteBuf writeBoolean(boolean b) { - return null; - } - - @Override - public ByteBuf writeByte(int i) { - return null; - } - - @Override - public ByteBuf writeShort(int i) { - return null; - } - - @Override - public ByteBuf writeMedium(int i) { - return null; - } - - @Override - public ByteBuf writeInt(int i) { - return null; - } - - @Override - public ByteBuf writeLong(long l) { - return null; - } - - @Override - public ByteBuf writeChar(int i) { - return null; - } - - @Override - public ByteBuf writeFloat(float v) { - return null; - } - - @Override - public ByteBuf writeDouble(double v) { - return null; - } - - @Override - public ByteBuf writeBytes(ByteBuf byteBuf) { - return null; - } - - @Override - public ByteBuf writeBytes(ByteBuf byteBuf, int i) { - return null; - } - - @Override - public ByteBuf writeBytes(ByteBuf byteBuf, int i, int i1) { - return null; - } - - @Override - public ByteBuf writeBytes(byte[] bytes) { - return null; - } - - @Override - public ByteBuf writeBytes(byte[] bytes, int i, int i1) { - return null; - } - - @Override - public ByteBuf writeBytes(ByteBuffer byteBuffer) { - return null; - } - - @Override - public int writeBytes(InputStream inputStream, int i) throws IOException { - return 0; - } - - @Override - public int writeBytes(ScatteringByteChannel scatteringByteChannel, int i) throws IOException { - return 0; - } - - @Override - public ByteBuf writeZero(int i) { - return null; - } - - @Override - public int indexOf(int i, int i1, byte b) { - return 0; - } - - @Override - public int bytesBefore(byte b) { - return 0; - } - - @Override - public int bytesBefore(int i, byte b) { - return 0; - } - - @Override - public int bytesBefore(int i, int i1, byte b) { - return 0; - } - - @Override - public int forEachByte(ByteBufProcessor byteBufProcessor) { - return 0; - } - - @Override - public int forEachByte(int i, int i1, ByteBufProcessor byteBufProcessor) { - return 0; - } - - @Override - public int forEachByteDesc(ByteBufProcessor byteBufProcessor) { - return 0; - } - - @Override - public int forEachByteDesc(int i, int i1, ByteBufProcessor byteBufProcessor) { - return 0; - } - - @Override - public ByteBuf copy() { - return null; - } - - @Override - public ByteBuf copy(int i, int i1) { - return null; - } - - @Override - public ByteBuf slice() { - return null; - } - - @Override - public ByteBuf slice(int i, int i1) { - return null; - } - - @Override - public ByteBuf duplicate() { - return null; - } - - @Override - public int nioBufferCount() { - return 0; - } - - @Override - public ByteBuffer nioBuffer() { - return null; - } - - @Override - public ByteBuffer nioBuffer(int i, int i1) { - return null; - } - - @Override - public ByteBuffer internalNioBuffer(int i, int i1) { - return null; - } - - @Override - public ByteBuffer[] nioBuffers() { - return new ByteBuffer[0]; - } - - @Override - public ByteBuffer[] nioBuffers(int i, int i1) { - return new ByteBuffer[0]; - } - - @Override - public boolean hasArray() { - return false; - } - - @Override - public byte[] array() { - return new byte[0]; - } - - @Override - public int arrayOffset() { - return 0; - } - - @Override - public boolean hasMemoryAddress() { - return false; - } - - @Override - public long memoryAddress() { - return 0; - } - - @Override - public String toString(Charset charset) { - return ""; - } - - @Override - public String toString(int i, int i1, Charset charset) { - return ""; - } - - @Override - public int hashCode() { - return 0; - } - - @Override - public boolean equals(Object o) { - return false; - } - - @Override - public int compareTo(ByteBuf byteBuf) { - return 0; - } - - @Override - public String toString() { - return null; - } - - @Override - public ByteBuf retain(int i) { - return null; - } - - @Override - public boolean release() { - return false; - } - - @Override - public boolean release(int i) { - return false; - } - - @Override - public int refCnt() { - return 0; - } - - @Override - public ByteBuf retain() { - return null; - } + + @Override + public int capacity() { + return 0; + } + + @Override + public ByteBuf capacity(int i) { + return null; + } + + @Override + public int maxCapacity() { + return 0; + } + + @Override + public ByteBufAllocator alloc() { + return null; + } + + @Override + public ByteOrder order() { + return null; + } + + @Override + public ByteBuf order(ByteOrder byteOrder) { + return null; + } + + @Override + public ByteBuf unwrap() { + return null; + } + + @Override + public boolean isDirect() { + return false; + } + + @Override + public int readerIndex() { + return 0; + } + + @Override + public ByteBuf readerIndex(int i) { + return null; + } + + @Override + public int writerIndex() { + return 0; + } + + @Override + public ByteBuf writerIndex(int i) { + return null; + } + + @Override + public ByteBuf setIndex(int i, int i1) { + return null; + } + + @Override + public int readableBytes() { + return 0; + } + + @Override + public int writableBytes() { + return 0; + } + + @Override + public int maxWritableBytes() { + return 0; + } + + @Override + public boolean isReadable() { + return false; + } + + @Override + public boolean isReadable(int i) { + return false; + } + + @Override + public boolean isWritable() { + return false; + } + + @Override + public boolean isWritable(int i) { + return false; + } + + @Override + public ByteBuf clear() { + return null; + } + + @Override + public ByteBuf markReaderIndex() { + return null; + } + + @Override + public ByteBuf resetReaderIndex() { + return null; + } + + @Override + public ByteBuf markWriterIndex() { + return null; + } + + @Override + public ByteBuf resetWriterIndex() { + return null; + } + + @Override + public ByteBuf discardReadBytes() { + return null; + } + + @Override + public ByteBuf discardSomeReadBytes() { + return null; + } + + @Override + public ByteBuf ensureWritable(int i) { + return null; + } + + @Override + public int ensureWritable(int i, boolean b) { + return 0; + } + + @Override + public boolean getBoolean(int i) { + return false; + } + + @Override + public byte getByte(int i) { + return 0; + } + + @Override + public short getUnsignedByte(int i) { + return 0; + } + + @Override + public short getShort(int i) { + return 0; + } + + @Override + public int getUnsignedShort(int i) { + return 0; + } + + @Override + public int getMedium(int i) { + return 0; + } + + @Override + public int getUnsignedMedium(int i) { + return 0; + } + + @Override + public int getInt(int i) { + return 0; + } + + @Override + public long getUnsignedInt(int i) { + return 0; + } + + @Override + public long getLong(int i) { + return 0; + } + + @Override + public char getChar(int i) { + return 0; + } + + @Override + public float getFloat(int i) { + return 0; + } + + @Override + public double getDouble(int i) { + return 0; + } + + @Override + public ByteBuf getBytes(int i, ByteBuf byteBuf) { + return null; + } + + @Override + public ByteBuf getBytes(int i, ByteBuf byteBuf, int i1) { + return null; + } + + @Override + public ByteBuf getBytes(int i, ByteBuf byteBuf, int i1, int i2) { + return null; + } + + @Override + public ByteBuf getBytes(int i, byte[] bytes) { + return null; + } + + @Override + public ByteBuf getBytes(int i, byte[] bytes, int i1, int i2) { + return null; + } + + @Override + public ByteBuf getBytes(int i, ByteBuffer byteBuffer) { + return null; + } + + @Override + public ByteBuf getBytes(int i, OutputStream outputStream, int i1) throws IOException { + return null; + } + + @Override + public int getBytes(int i, GatheringByteChannel gatheringByteChannel, int i1) throws IOException { + return 0; + } + + @Override + public ByteBuf setBoolean(int i, boolean b) { + return null; + } + + @Override + public ByteBuf setByte(int i, int i1) { + return null; + } + + @Override + public ByteBuf setShort(int i, int i1) { + return null; + } + + @Override + public ByteBuf setMedium(int i, int i1) { + return null; + } + + @Override + public ByteBuf setInt(int i, int i1) { + return null; + } + + @Override + public ByteBuf setLong(int i, long l) { + return null; + } + + @Override + public ByteBuf setChar(int i, int i1) { + return null; + } + + @Override + public ByteBuf setFloat(int i, float v) { + return null; + } + + @Override + public ByteBuf setDouble(int i, double v) { + return null; + } + + @Override + public ByteBuf setBytes(int i, ByteBuf byteBuf) { + return null; + } + + @Override + public ByteBuf setBytes(int i, ByteBuf byteBuf, int i1) { + return null; + } + + @Override + public ByteBuf setBytes(int i, ByteBuf byteBuf, int i1, int i2) { + return null; + } + + @Override + public ByteBuf setBytes(int i, byte[] bytes) { + return null; + } + + @Override + public ByteBuf setBytes(int i, byte[] bytes, int i1, int i2) { + return null; + } + + @Override + public ByteBuf setBytes(int i, ByteBuffer byteBuffer) { + return null; + } + + @Override + public int setBytes(int i, InputStream inputStream, int i1) { + return 0; + } + + @Override + public int setBytes(int i, ScatteringByteChannel scatteringByteChannel, int i1) { + return 0; + } + + @Override + public ByteBuf setZero(int i, int i1) { + return null; + } + + @Override + public boolean readBoolean() { + return false; + } + + @Override + public byte readByte() { + return 0; + } + + @Override + public short readUnsignedByte() { + return 0; + } + + @Override + public short readShort() { + return 0; + } + + @Override + public int readUnsignedShort() { + return 0; + } + + @Override + public int readMedium() { + return 0; + } + + @Override + public int readUnsignedMedium() { + return 0; + } + + @Override + public int readInt() { + return 0; + } + + @Override + public long readUnsignedInt() { + return 0; + } + + @Override + public long readLong() { + return 0; + } + + @Override + public char readChar() { + return 0; + } + + @Override + public float readFloat() { + return 0; + } + + @Override + public double readDouble() { + return 0; + } + + @Override + public ByteBuf readBytes(int i) { + return null; + } + + @Override + public ByteBuf readSlice(int i) { + return null; + } + + @Override + public ByteBuf readBytes(ByteBuf byteBuf) { + return null; + } + + @Override + public ByteBuf readBytes(ByteBuf byteBuf, int i) { + return null; + } + + @Override + public ByteBuf readBytes(ByteBuf byteBuf, int i, int i1) { + return null; + } + + @Override + public ByteBuf readBytes(byte[] bytes) { + return null; + } + + @Override + public ByteBuf readBytes(byte[] bytes, int i, int i1) { + return null; + } + + @Override + public ByteBuf readBytes(ByteBuffer byteBuffer) { + return null; + } + + @Override + public ByteBuf readBytes(OutputStream outputStream, int i) { + return null; + } + + @Override + public int readBytes(GatheringByteChannel gatheringByteChannel, int i) { + return 0; + } + + @Override + public ByteBuf skipBytes(int i) { + return null; + } + + @Override + public ByteBuf writeBoolean(boolean b) { + return null; + } + + @Override + public ByteBuf writeByte(int i) { + return null; + } + + @Override + public ByteBuf writeShort(int i) { + return null; + } + + @Override + public ByteBuf writeMedium(int i) { + return null; + } + + @Override + public ByteBuf writeInt(int i) { + return null; + } + + @Override + public ByteBuf writeLong(long l) { + return null; + } + + @Override + public ByteBuf writeChar(int i) { + return null; + } + + @Override + public ByteBuf writeFloat(float v) { + return null; + } + + @Override + public ByteBuf writeDouble(double v) { + return null; + } + + @Override + public ByteBuf writeBytes(ByteBuf byteBuf) { + return null; + } + + @Override + public ByteBuf writeBytes(ByteBuf byteBuf, int i) { + return null; + } + + @Override + public ByteBuf writeBytes(ByteBuf byteBuf, int i, int i1) { + return null; + } + + @Override + public ByteBuf writeBytes(byte[] bytes) { + return null; + } + + @Override + public ByteBuf writeBytes(byte[] bytes, int i, int i1) { + return null; + } + + @Override + public ByteBuf writeBytes(ByteBuffer byteBuffer) { + return null; + } + + @Override + public int writeBytes(InputStream inputStream, int i) { + return 0; + } + + @Override + public int writeBytes(ScatteringByteChannel scatteringByteChannel, int i) { + return 0; + } + + @Override + public ByteBuf writeZero(int i) { + return null; + } + + @Override + public int indexOf(int i, int i1, byte b) { + return 0; + } + + @Override + public int bytesBefore(byte b) { + return 0; + } + + @Override + public int bytesBefore(int i, byte b) { + return 0; + } + + @Override + public int bytesBefore(int i, int i1, byte b) { + return 0; + } + + @Override + public int forEachByte(ByteBufProcessor byteBufProcessor) { + return 0; + } + + @Override + public int forEachByte(int i, int i1, ByteBufProcessor byteBufProcessor) { + return 0; + } + + @Override + public int forEachByteDesc(ByteBufProcessor byteBufProcessor) { + return 0; + } + + @Override + public int forEachByteDesc(int i, int i1, ByteBufProcessor byteBufProcessor) { + return 0; + } + + @Override + public ByteBuf copy() { + return null; + } + + @Override + public ByteBuf copy(int i, int i1) { + return null; + } + + @Override + public ByteBuf slice() { + return null; + } + + @Override + public ByteBuf slice(int i, int i1) { + return null; + } + + @Override + public ByteBuf duplicate() { + return null; + } + + @Override + public int nioBufferCount() { + return 0; + } + + @Override + public ByteBuffer nioBuffer() { + return null; + } + + @Override + public ByteBuffer nioBuffer(int i, int i1) { + return null; + } + + @Override + public ByteBuffer internalNioBuffer(int i, int i1) { + return null; + } + + @Override + public ByteBuffer[] nioBuffers() { + return new ByteBuffer[0]; + } + + @Override + public ByteBuffer[] nioBuffers(int i, int i1) { + return new ByteBuffer[0]; + } + + @Override + public boolean hasArray() { + return false; + } + + @Override + public byte[] array() { + return new byte[0]; + } + + @Override + public int arrayOffset() { + return 0; + } + + @Override + public boolean hasMemoryAddress() { + return false; + } + + @Override + public long memoryAddress() { + return 0; + } + + @Override + public String toString(Charset charset) { + return ""; + } + + @Override + public String toString(int i, int i1, Charset charset) { + return ""; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean equals(Object o) { + return false; + } + + @Override + public int compareTo(ByteBuf byteBuf) { + return 0; + } + + @Override + public String toString() { + return null; + } + + @Override + public ByteBuf retain(int i) { + return null; + } + + @Override + public boolean release() { + return false; + } + + @Override + public boolean release(int i) { + return false; + } + + @Override + public int refCnt() { + return 0; + } + + @Override + public ByteBuf retain() { + return null; + } } \ No newline at end of file diff --git a/src/main/java/com/comphenix/protocol/wrappers/AutoWrapper.java b/src/main/java/com/comphenix/protocol/wrappers/AutoWrapper.java index 02e295f7..5954910b 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/AutoWrapper.java +++ b/src/main/java/com/comphenix/protocol/wrappers/AutoWrapper.java @@ -137,14 +137,14 @@ public class AutoWrapper implements EquivalentConverter { nmsAccessors = Arrays .stream(nmsClass.getDeclaredFields()) .filter(field -> !Modifier.isStatic(field.getModifiers())) - .map(field -> Accessors.getFieldAccessor(field, true)) + .map(field -> Accessors.getFieldAccessor(field)) .toArray(FieldAccessor[]::new); } if (wrapperAccessors == null) { wrapperAccessors = Arrays .stream(wrapperClass.getDeclaredFields()) - .map(field -> Accessors.getFieldAccessor(field, true)) + .map(field -> Accessors.getFieldAccessor(field)) .toArray(FieldAccessor[]::new); } } diff --git a/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java index 63e2a24d..ebc11ab4 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java +++ b/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java @@ -577,7 +577,7 @@ public class BukkitConverters { @Override public WrappedWatchableObject getSpecific(Object generic) { - if (MinecraftReflection.isWatchableObject(generic)) + if (MinecraftReflection.is(MinecraftReflection.getDataWatcherItemClass(), generic)) return new WrappedWatchableObject(generic); else if (generic instanceof WrappedWatchableObject) return (WrappedWatchableObject) generic; @@ -638,7 +638,7 @@ public class BukkitConverters { // Deduce getType method by parameters alone if (worldTypeGetType == null) { worldTypeGetType = FuzzyReflection.fromClass(worldType). - getMethodByParameters("getType", worldType, new Class[]{String.class}); + getMethodByReturnTypeAndParameters("getType", worldType, new Class[]{String.class}); } // Convert to the Bukkit world type @@ -658,7 +658,7 @@ public class BukkitConverters { } catch (Exception e) { // Assume the first method is the one worldTypeName = FuzzyReflection.fromClass(worldType). - getMethodByParameters("name", String.class, new Class[]{}); + getMethodByReturnTypeAndParameters("name", String.class, new Class[]{}); } } @@ -931,7 +931,7 @@ public class BukkitConverters { @Override public PotionEffect getSpecific(Object generic) { if (mobEffectModifier == null) { - mobEffectModifier = new StructureModifier<>(MinecraftReflection.getMobEffectClass(), false); + mobEffectModifier = new StructureModifier<>(MinecraftReflection.getMobEffectClass()); } StructureModifier ints = mobEffectModifier.withTarget(generic).withType(int.class); StructureModifier bools = mobEffectModifier.withTarget(generic).withType(boolean.class); @@ -987,7 +987,7 @@ public class BukkitConverters { @Override public Vector getSpecific(Object generic) { if (vec3dModifier == null) { - vec3dModifier = new StructureModifier<>(MinecraftReflection.getVec3DClass(), false); + vec3dModifier = new StructureModifier<>(MinecraftReflection.getVec3DClass()); } StructureModifier doubles = vec3dModifier.withTarget(generic).withType(double.class); @@ -1018,19 +1018,19 @@ public class BukkitConverters { Class craftSound = MinecraftReflection.getCraftSoundClass(); FuzzyReflection fuzzy = FuzzyReflection.fromClass(craftSound, true); - getSoundEffectByKey = Accessors.getMethodAccessor(fuzzy.getMethodByParameters( + getSoundEffectByKey = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters( "getSoundEffect", MinecraftReflection.getSoundEffectClass(), new Class[]{String.class} )); - getSoundEffectBySound = Accessors.getMethodAccessor(fuzzy.getMethodByParameters( + getSoundEffectBySound = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters( "getSoundEffect", MinecraftReflection.getSoundEffectClass(), new Class[]{Sound.class} )); - getSoundByEffect = Accessors.getMethodAccessor(fuzzy.getMethodByParameters( + getSoundByEffect = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters( "getBukkit", Sound.class, new Class[]{MinecraftReflection.getSoundEffectClass()} @@ -1062,8 +1062,8 @@ public class BukkitConverters { Class craftSound = MinecraftReflection.getCraftSoundClass(); FuzzyReflection fuzzy = FuzzyReflection.fromClass(craftSound, true); getSound = Accessors.getMethodAccessor( - fuzzy.getMethodByParameters("getSound", String.class, new Class[]{Sound.class})); - getSoundEffect = Accessors.getMethodAccessor(fuzzy.getMethodByParameters("getSoundEffect", + fuzzy.getMethodByReturnTypeAndParameters("getSound", String.class, new Class[]{Sound.class})); + getSoundEffect = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters("getSoundEffect", MinecraftReflection.getSoundEffectClass(), new Class[]{String.class})); } diff --git a/src/main/java/com/comphenix/protocol/wrappers/ChunkCoordIntPair.java b/src/main/java/com/comphenix/protocol/wrappers/ChunkCoordIntPair.java index 5a430170..f7d0803d 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/ChunkCoordIntPair.java +++ b/src/main/java/com/comphenix/protocol/wrappers/ChunkCoordIntPair.java @@ -37,15 +37,6 @@ public class ChunkCoordIntPair { this.chunkZ = z; } - /** - * Retrieve the equivalent chunk position. - * @param y - the y position. - * @return The chunk position. - */ - public ChunkPosition getPosition(int y) { - return new ChunkPosition((chunkX << 4) + 8, y, (chunkZ << 4) + 8); - } - /** * Retrieve the chunk index in the x-dimension. *

    diff --git a/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java b/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java deleted file mode 100644 index ae0dc1d5..00000000 --- a/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * 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; - -import org.bukkit.util.Vector; - -import com.comphenix.protocol.reflect.EquivalentConverter; -import com.comphenix.protocol.reflect.FieldAccessException; -import com.comphenix.protocol.reflect.StructureModifier; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.google.common.base.Objects; - -/** - * Copies a immutable net.minecraft.server.ChunkPosition, which represents a integer 3D vector. - * - * @author Kristian - */ -public class ChunkPosition { - - /** - * Represents the null (0, 0, 0) origin. - */ - public static ChunkPosition ORIGIN = new ChunkPosition(0, 0, 0); - - private static Constructor chunkPositionConstructor; - - // Use protected members, like Bukkit - protected final int x; - protected final int y; - protected final int z; - - // Used to access a ChunkPosition, in case it's names are changed - private static StructureModifier intModifier; - - /** - * Construct an immutable 3D vector. - * @param x - x coordinate - * @param y - y coordinate - * @param z - z coordinate - */ - public ChunkPosition(int x, int y, int z) { - this.x = x; - this.y = y; - this.z = z; - } - - /** - * Construct an immutable integer 3D vector from a mutable Bukkit vector. - * @param vector - the mutable real Bukkit vector to copy. - */ - public ChunkPosition(Vector vector) { - if (vector == null) - throw new IllegalArgumentException("Vector cannot be NULL."); - this.x = vector.getBlockX(); - this.y = vector.getBlockY(); - this.z = vector.getBlockZ(); - } - - /** - * Convert this instance to an equivalent real 3D vector. - * @return Real 3D vector. - */ - public Vector toVector() { - return new Vector(x, y, z); - } - - /** - * Retrieve the x-coordinate. - * @return X coordinate. - */ - public int getX() { - return x; - } - - /** - * Retrieve the y-coordinate. - * @return Y coordinate. - */ - public int getY() { - return y; - } - - /** - * Retrieve the z-coordinate. - * @return Z coordinate. - */ - public int getZ() { - return z; - } - - /** - * Adds the current position and a given position together, producing a result position. - * @param other - the other position. - * @return The new result position. - */ - public ChunkPosition add(ChunkPosition other) { - if (other == null) - throw new IllegalArgumentException("other cannot be NULL"); - return new ChunkPosition(x + other.x, y + other.y, z + other.z); - } - - /** - * Adds the current position and a given position together, producing a result position. - * @param other - the other position. - * @return The new result position. - */ - public ChunkPosition subtract(ChunkPosition other) { - if (other == null) - throw new IllegalArgumentException("other cannot be NULL"); - return new ChunkPosition(x - other.x, y - other.y, z - other.z); - } - - /** - * Multiply each dimension in the current position by the given factor. - * @param factor - multiplier. - * @return The new result. - */ - public ChunkPosition multiply(int factor) { - return new ChunkPosition(x * factor, y * factor, z * factor); - } - - /** - * Divide each dimension in the current position by the given divisor. - * @param divisor - the divisor. - * @return The new result. - */ - public ChunkPosition divide(int divisor) { - if (divisor == 0) - throw new IllegalArgumentException("Cannot divide by null."); - return new ChunkPosition(x / divisor, y / divisor, z / divisor); - } - - /** - * Used to convert between NMS ChunkPosition and the wrapper instance. - * @return A new converter. - */ - public static EquivalentConverter getConverter() { - return new EquivalentConverter() { - @Override - public Object getGeneric(ChunkPosition specific) { - if (chunkPositionConstructor == null) { - try { - chunkPositionConstructor = MinecraftReflection.getChunkPositionClass(). - getConstructor(int.class, int.class, int.class); - } catch (Exception e) { - throw new RuntimeException("Cannot find chunk position constructor.", e); - } - } - - // Construct the underlying ChunkPosition - try { - Object result = chunkPositionConstructor.newInstance(specific.x, specific.y, specific.z); - return result; - } catch (Exception e) { - throw new RuntimeException("Cannot construct ChunkPosition.", e); - } - } - - @Override - public ChunkPosition getSpecific(Object generic) { - if (MinecraftReflection.isChunkPosition(generic)) { - // Use a structure modifier - intModifier = new StructureModifier<>(generic.getClass(), null, false).withType(int.class); - - // Damn it all - if (intModifier.size() < 3) { - throw new IllegalStateException("Cannot read class " + generic.getClass() + " for its integer fields."); - } - - if (intModifier.size() >= 3) { - try { - StructureModifier instance = intModifier.withTarget(generic); - ChunkPosition result = new ChunkPosition(instance.read(0), instance.read(1), instance.read(2)); - return result; - } 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); - } - } - } - - // Otherwise, return NULL - return null; - } - - // Thanks Java Generics! - @Override - public Class getSpecificType() { - return ChunkPosition.class; - } - }; - } - - @Override - public boolean equals(Object obj) { - // Fast checks - if (this == obj) return true; - if (obj == null) return false; - - // Only compare objects of similar type - if (obj instanceof ChunkPosition) { - ChunkPosition other = (ChunkPosition) obj; - return x == other.x && y == other.y && z == other.z; - } - return false; - } - - @Override - public int hashCode() { - return Objects.hashCode(x, y, z); - } - - @Override - public String toString() { - return "WrappedChunkPosition [x=" + x + ", y=" + y + ", z=" + z + "]"; - } -} diff --git a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java index 3bdac060..6b6c78cf 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java +++ b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java @@ -1,5 +1,6 @@ package com.comphenix.protocol.wrappers; +import com.comphenix.protocol.reflect.ExactReflection; import java.lang.reflect.Field; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -471,8 +472,7 @@ public abstract class EnumWrappers { * Initialize the wrappers, if we haven't already. */ private static void initialize() { - if (!MinecraftReflection.isUsingNetty()) - throw new IllegalArgumentException("Not supported on 1.6.4 and earlier."); + if (INITIALIZED) return; @@ -920,10 +920,8 @@ public abstract class EnumWrappers { public Object getGeneric(T specific) { Validate.notNull(specific, "specific object cannot be null"); - return Accessors - .getFieldAccessor(genericClass, specific - .name(), false) - .get(null); + Field field = ExactReflection.fromClass(this.genericClass, false).findField(specific.name()); + return Accessors.getFieldAccessor(field).get(null); } @Override diff --git a/src/main/java/com/comphenix/protocol/wrappers/TroveWrapper.java b/src/main/java/com/comphenix/protocol/wrappers/TroveWrapper.java index 7108d603..c1eb558f 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/TroveWrapper.java +++ b/src/main/java/com/comphenix/protocol/wrappers/TroveWrapper.java @@ -1,6 +1,5 @@ package com.comphenix.protocol.wrappers; -import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; @@ -10,9 +9,7 @@ import java.util.Set; import javax.annotation.Nonnull; import com.comphenix.protocol.reflect.FieldAccessException; -import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import com.comphenix.protocol.reflect.accessors.ReadOnlyFieldAccessor; import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers; import com.comphenix.protocol.utility.ClassSource; @@ -39,7 +36,7 @@ public class TroveWrapper { * @param accessor - the accessor. * @return The read only accessor. */ - public static ReadOnlyFieldAccessor wrapMapField(final FieldAccessor accessor) { + public static FieldAccessor wrapMapField(final FieldAccessor accessor) { return wrapMapField(accessor, null); } @@ -49,8 +46,8 @@ public class TroveWrapper { * @param noEntryTransform - transform the no entry value, or NULL to ignore. * @return The read only accessor. */ - public static ReadOnlyFieldAccessor wrapMapField(final FieldAccessor accessor, final Function noEntryTransform) { - return new ReadOnlyFieldAccessor() { + public static FieldAccessor wrapMapField(final FieldAccessor accessor, final Function noEntryTransform) { + /*return new ReadOnlyFieldAccessor() { @Override public Object get(Object instance) { Object troveMap = accessor.get(instance); @@ -64,43 +61,8 @@ public class TroveWrapper { public Field getField() { return accessor.getField(); } - }; - } - - /** - * Retrieve a read-only field accessor that automatically wraps the underlying Trove instance. - * @param accessor - the accessor. - * @return The read only accessor. - */ - public static ReadOnlyFieldAccessor wrapSetField(final FieldAccessor accessor) { - return new ReadOnlyFieldAccessor() { - @Override - public Object get(Object instance) { - return getDecoratedSet(accessor.get(instance)); - } - @Override - public Field getField() { - return accessor.getField(); - } - }; - } - - /** - * Retrieve a read-only field accessor that automatically wraps the underlying Trove instance. - * @param accessor - the accessor. - * @return The read only accessor. - */ - public static ReadOnlyFieldAccessor wrapListField(final FieldAccessor accessor) { - return new ReadOnlyFieldAccessor() { - @Override - public Object get(Object instance) { - return getDecoratedList(accessor.get(instance)); - } - @Override - public Field getField() { - return accessor.getField(); - } - }; + };*/ + return null; } /** @@ -158,28 +120,6 @@ public class TroveWrapper { return getClassSource(clazz) != null; } - /** - * Transform the no entry value in the given map. - * @param troveMap - the trove map. - * @param transform - the transform. - */ - public static void transformNoEntryValue(Object troveMap, Function transform) { - // Check for stupid no_entry_values - try { - Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true); - int current = (Integer) FieldUtils.readField(field, troveMap, true); - int transformed = transform.apply(current); - - if (current != transformed) { - FieldUtils.writeField(field, troveMap, transformed); - } - } catch (IllegalArgumentException e) { - throw new CannotFindTroveNoEntryValue(e); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Cannot access reflection.", e); - } - } - /** * Retrieve the correct class source from the given class. * @param clazz - the class source. diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedBlockData.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedBlockData.java index 50ceb69c..c3bf4bec 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedBlockData.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedBlockData.java @@ -189,11 +189,11 @@ public abstract class WrappedBlockData extends AbstractWrapper implements Clonab TO_LEGACY_DATA = Accessors.getMethodAccessor(fuzzy.getMethod(contract, "toLegacyData")); fuzzy = FuzzyReflection.fromClass(MAGIC_NUMBERS); - GET_NMS_BLOCK = Accessors.getMethodAccessor(fuzzy.getMethodByParameters("getBlock", BLOCK, + GET_NMS_BLOCK = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters("getBlock", BLOCK, new Class[]{Material.class})); fuzzy = FuzzyReflection.fromClass(IBLOCK_DATA); - GET_BLOCK = Accessors.getMethodAccessor(fuzzy.getMethodByParameters("getBlock", BLOCK, + GET_BLOCK = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters("getBlock", BLOCK, new Class[0])); } } diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java index d8f1e87d..a12a49b7 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java @@ -5,7 +5,6 @@ import java.io.StringReader; import org.bukkit.ChatColor; -import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; @@ -34,18 +33,14 @@ public class WrappedChatComponent extends AbstractWrapper implements ClonableWra FuzzyReflection fuzzy = FuzzyReflection.fromClass(SERIALIZER, true); // Retrieve the correct methods - SERIALIZE_COMPONENT = Accessors.getMethodAccessor(fuzzy.getMethodByParameters("serialize", /* a */ + SERIALIZE_COMPONENT = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters("serialize", /* a */ String.class, new Class[] { COMPONENT })); - try { - GSON = FieldUtils.readStaticField(fuzzy.getFieldByType("gson", GSON_CLASS), true); - } catch (IllegalAccessException ex) { - throw new RuntimeException("Failed to obtain GSON field", ex); - } + GSON = Accessors.getFieldAccessor(fuzzy.getFieldByType("gson", GSON_CLASS)).get(null); try { DESERIALIZE = Accessors.getMethodAccessor(FuzzyReflection.fromClass(MinecraftReflection.getChatDeserializer(), true) - .getMethodByParameters("deserialize", Object.class, new Class[] { GSON_CLASS, String.class, Class.class, boolean.class })); + .getMethodByReturnTypeAndParameters("deserialize", Object.class, new Class[] { GSON_CLASS, String.class, Class.class, boolean.class })); } catch (IllegalArgumentException ex) { // We'll handle it in the ComponentParser DESERIALIZE = null; diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java deleted file mode 100644 index ee1bb097..00000000 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * 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; -import com.comphenix.protocol.utility.MinecraftReflection; - -/** - * Allows access to a chunk coordinate. - * - * @author Kristian - */ -public class WrappedChunkCoordinate extends AbstractWrapper implements Comparable { - /** - * If TRUE, NULLs should be put before non-null instances of this class. - */ - private static final boolean LARGER_THAN_NULL = true; - - // Used to access a ChunkCoordinate - private static StructureModifier SHARED_MODIFIER; - - // The current modifier - private StructureModifier handleModifier; - - /** - * Create a new empty wrapper. - */ - public WrappedChunkCoordinate() { - super(MinecraftReflection.getChunkCoordinatesClass()); - - try { - setHandle(getHandleType().newInstance()); - } catch (Exception e) { - throw new RuntimeException("Cannot construct chunk coordinate."); - } - } - - /** - * Create a wrapper for a specific chunk coordinates. - * @param handle - the NMS chunk coordinates. - */ - @SuppressWarnings("rawtypes") - public WrappedChunkCoordinate(Comparable handle) { - super(MinecraftReflection.getChunkCoordinatesClass()); - setHandle(handle); - } - - // Ensure that the structure modifier is initialized - private StructureModifier getModifier() { - if (SHARED_MODIFIER == null) - SHARED_MODIFIER = new StructureModifier(handle.getClass(), null, false).withType(int.class); - if (handleModifier == null) - handleModifier = SHARED_MODIFIER.withTarget(handle); - return handleModifier; - } - - /** - * Create a wrapper with specific values. - * @param x - the x coordinate. - * @param y - the y coordinate. - * @param z - the z coordinate. - */ - public WrappedChunkCoordinate(int x, int y, int z) { - this(); - setX(x); - setY(y); - setZ(z); - } - - /** - * Create a chunk coordinate wrapper from a given position. - * @param position - the given position. - */ - public WrappedChunkCoordinate(ChunkPosition position) { - this(position.getX(), position.getY(), position.getZ()); - } - - public Object getHandle() { - return handle; - } - - /** - * Retrieve the x coordinate of the underlying coordinate. - * @return The x coordinate. - */ - public int getX() { - return getModifier().read(0); - } - - /** - * Set the x coordinate of the underlying coordinate. - * @param newX - the new x coordinate. - */ - public void setX(int newX) { - getModifier().write(0, newX); - } - - /** - * Retrieve the y coordinate of the underlying coordinate. - * @return The y coordinate. - */ - public int getY() { - return getModifier().read(1); - } - - /** - * Set the y coordinate of the underlying coordinate. - * @param newY - the new y coordinate. - */ - public void setY(int newY) { - getModifier().write(1, newY); - } - - /** - * Retrieve the z coordinate of the underlying coordinate. - * @return The z coordinate. - */ - public int getZ() { - return getModifier().read(2); - } - - /** - * Set the z coordinate of the underlying coordiate. - * @param newZ - the new z coordinate. - */ - public void setZ(int newZ) { - getModifier().write(2, newZ); - } - - /** - * Create an immutable chunk position from this coordinate. - * @return The new immutable chunk position. - */ - public ChunkPosition toPosition() { - return new ChunkPosition(getX(), getY(), getZ()); - } - - @SuppressWarnings("unchecked") - @Override - public int compareTo(WrappedChunkCoordinate other) { - // We'll handle NULL objects too, unlike ChunkCoordinates - if (other.handle == null) - return LARGER_THAN_NULL ? -1 : 1; - else - return ((Comparable) handle).compareTo(other.handle); - } - - @Override - public String toString() { - return String.format("ChunkCoordinate [x: %s, y: %s, z: %s]", getX(), getY(), getZ()); - } -} diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java index 0a5b0b3b..65081063 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java @@ -328,16 +328,6 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable GSON_CLASS = MinecraftReflection.getMinecraftGsonClass(); private static MethodAccessor GSON_TO_JSON = Accessors.getMethodAccessor(GSON_CLASS, "toJson", Object.class); private static MethodAccessor GSON_FROM_JSON = Accessors.getMethodAccessor(GSON_CLASS, "fromJson", String.class, Class.class); - private static FieldAccessor PING_GSON = Accessors.getCached(Accessors.getFieldAccessor( + private static FieldAccessor PING_GSON = Accessors.getMemorizing(Accessors.getFieldAccessor( PacketType.Status.Server.SERVER_INFO.getPacketClass(), GSON_CLASS, true )); diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedStatistic.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedStatistic.java index bcc3b426..dc8b1417 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedStatistic.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedStatistic.java @@ -8,8 +8,6 @@ import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.utility.MinecraftReflection; -import com.google.common.base.Function; -import com.google.common.collect.Iterables; /** * Represents a Minecraft statistics. @@ -23,7 +21,7 @@ public class WrappedStatistic extends AbstractWrapper { static { try { FIND_STATISTICS = Accessors.getMethodAccessor( - FuzzyReflection.fromClass(STATISTIC_LIST).getMethodByParameters( + FuzzyReflection.fromClass(STATISTIC_LIST).getMethodByReturnTypeAndParameters( "findStatistic", STATISTIC, new Class[]{String.class})); MAP_ACCESSOR = Accessors.getFieldAccessor(STATISTIC_LIST, Map.class, true); GET_NAME = Accessors.getFieldAccessor(STATISTIC, String.class, true); diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java index 14051a8a..52de8876 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java @@ -1,25 +1,21 @@ /** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2016 dmulloy2 - * - * 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 + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2016 dmulloy2 + *

    + * 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 static com.comphenix.protocol.utility.MinecraftReflection.is; -import org.bukkit.inventory.ItemStack; - import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; @@ -27,37 +23,40 @@ import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.wrappers.EnumWrappers.Direction; import com.comphenix.protocol.wrappers.WrappedDataWatcher.Serializer; import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; -import com.comphenix.protocol.wrappers.nbt.NbtBase; import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtFactory; -import com.google.common.base.Optional; +import java.util.Optional; +import org.bukkit.inventory.ItemStack; /** - * Represents a DataWatcher Item in 1.8 thru 1.10. + * Represents a DataWatcher Item in 1.8 to 1.10. + * * @author dmulloy2 */ public class WrappedWatchableObject extends AbstractWrapper { + private static final Class HANDLE_TYPE = MinecraftReflection.getDataWatcherItemClass(); private static Integer VALUE_INDEX = null; - private static ConstructorAccessor constructor; private final StructureModifier modifier; /** * Constructs a DataWatcher Item wrapper from an existing NMS data watcher item. + * * @param handle Data watcher item */ public WrappedWatchableObject(Object handle) { super(HANDLE_TYPE); - setHandle(handle); - this.modifier = new StructureModifier(handleType).withTarget(handle); + this.setHandle(handle); + this.modifier = new StructureModifier<>(this.handleType).withTarget(handle); } /** * Constructs a DataWatcher Item wrapper from a given index and initial value. *

    * Not recommended in 1.9 and up. + * * @param index Index of the Item * @param value Initial value */ @@ -67,8 +66,9 @@ public class WrappedWatchableObject extends AbstractWrapper { /** * Constructs a DataWatcher Item wrapper from a given watcher object and initial value. + * * @param watcherObject Watcher object - * @param value Initial value + * @param value Initial value */ public WrappedWatchableObject(WrappedDataWatcherObject watcherObject, Object value) { this(newHandle(watcherObject, value)); @@ -90,125 +90,12 @@ public class WrappedWatchableObject extends AbstractWrapper { // ---- Getter methods /** - * Gets this Item's watcher object, which contains the index and serializer. - * @return The watcher object - */ - public WrappedDataWatcherObject getWatcherObject() { - return new WrappedDataWatcherObject(modifier.read(0)); - } - - /** - * Gets this Item's index from the watcher object - * @return The index - */ - public int getIndex() { - if (MinecraftReflection.watcherObjectExists()) { - return getWatcherObject().getIndex(); - } - - return modifier.withType(int.class).read(1); - } - - /** - * Gets the wrapped value of this data watcher item. - * @return The wrapped value - */ - public Object getValue() { - return getWrapped(getRawValue()); - } - - /** - * Gets the raw value of this data watcher item. - * @return Raw value - */ - public Object getRawValue() { - if (VALUE_INDEX == null) { - VALUE_INDEX = MinecraftReflection.watcherObjectExists() ? 1 : 2; - } - - return modifier.readSafely(VALUE_INDEX); - } - - /** - * Sets the value of this item. - * @param value New value - * @param updateClient Whether or not to update the client - */ - public void setValue(Object value, boolean updateClient) { - if (VALUE_INDEX == null) { - VALUE_INDEX = MinecraftReflection.watcherObjectExists() ? 1 : 2; - } - - modifier.write(VALUE_INDEX, getUnwrapped(value)); - - if (updateClient) { - setDirtyState(true); - } - } - - /** - * Sets the value of this item. - * @param value New value - */ - public void setValue(Object value) { - setValue(value, false); - } - - /** - * Whether or not the value must be synchronized with the client. - * @return True if it must, false if not - */ - public boolean getDirtyState() { - return modifier.withType(boolean.class).read(0); - } - - /** - * Sets this item's dirty state - * @param dirty New state - */ - public void setDirtyState(boolean dirty) { - modifier.withType(boolean.class).write(0, dirty); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - - if (obj instanceof WrappedWatchableObject) { - WrappedWatchableObject that = (WrappedWatchableObject) obj; - return this.getIndex() == that.getIndex() && - this.getRawValue().equals(that.getRawValue()) && - this.getDirtyState() == that.getDirtyState(); - } - - return false; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + getIndex(); - result = prime * result + getRawValue().hashCode(); - result = prime * result + (getDirtyState() ? 1231 : 1237); - return result; - } - - @Override - public String toString() { - return "DataWatcherItem[index=" + getIndex() + ", value=" + getValue() + ", dirty=" + getDirtyState() + "]"; - } - - // ---- Wrapping - - /** - * Retrieve the wrapped object value, if needed. All non-primitive objects - * with {@link Serializer}s should be covered by this. - * + * Retrieve the wrapped object value, if needed. All non-primitive objects with {@link Serializer}s should be covered + * by this. + * * @param value - the raw NMS object to wrap. * @return The wrapped object. */ - @SuppressWarnings("rawtypes") static Object getWrapped(Object value) { // Handle watcher items first if (is(MinecraftReflection.getDataWatcherItemClass(), value)) { @@ -216,13 +103,8 @@ public class WrappedWatchableObject extends AbstractWrapper { } // Then deal with optionals - if (value instanceof Optional) { - Optional optional = (Optional) value; - if (optional.isPresent()) { - return Optional.of(getWrapped(optional.get())); - } else { - return Optional.absent(); - } + if (value instanceof Optional) { + return ((Optional) value).map(WrappedWatchableObject::getWrapped); } // Current supported classes @@ -232,7 +114,7 @@ public class WrappedWatchableObject extends AbstractWrapper { return BukkitConverters.getItemStackConverter().getSpecific(value); } else if (is(MinecraftReflection.getIBlockDataClass(), value)) { return BukkitConverters.getWrappedBlockDataConverter().getSpecific(value); - } else if (is (Vector3F.getMinecraftClass(), value)) { + } else if (is(Vector3F.getMinecraftClass(), value)) { return Vector3F.getConverter().getSpecific(value); } else if (is(MinecraftReflection.getBlockPositionClass(), value)) { return BlockPosition.getConverter().getSpecific(value); @@ -242,31 +124,19 @@ public class WrappedWatchableObject extends AbstractWrapper { return NbtFactory.fromNMSCompound(value); } - // Legacy classes - if (is(MinecraftReflection.getChunkCoordinatesClass(), value)) { - return new WrappedChunkCoordinate((Comparable) value); - } else if (is(MinecraftReflection.getChunkPositionClass(), value)) { - return ChunkPosition.getConverter().getSpecific(value); - } - return value; } /** * Retrieve the raw NMS value. - * + * * @param wrapped - the wrapped position. * @return The raw NMS object. */ // Must be kept in sync with getWrapped! static Object getUnwrapped(Object wrapped) { - if (wrapped instanceof Optional) { - Optional optional = (Optional) wrapped; - if (optional.isPresent()) { - return Optional.of(getUnwrapped(optional.get())); - } else { - return Optional.absent(); - } + if (wrapped instanceof Optional) { + return ((Optional) wrapped).map(WrappedWatchableObject::getUnwrapped); } // Current supported classes @@ -286,13 +156,126 @@ public class WrappedWatchableObject extends AbstractWrapper { return NbtFactory.fromBase((NbtCompound) wrapped).getHandle(); } - // Legacy classes - if (wrapped instanceof ChunkPosition) { - return ChunkPosition.getConverter().getGeneric((ChunkPosition) wrapped); - } else if (wrapped instanceof WrappedChunkCoordinate) { - return ((WrappedChunkCoordinate) wrapped).getHandle(); - } - return wrapped; } + + /** + * Gets this Item's watcher object, which contains the index and serializer. + * + * @return The watcher object + */ + public WrappedDataWatcherObject getWatcherObject() { + return new WrappedDataWatcherObject(this.modifier.read(0)); + } + + /** + * Gets this Item's index from the watcher object + * + * @return The index + */ + public int getIndex() { + if (MinecraftReflection.watcherObjectExists()) { + return this.getWatcherObject().getIndex(); + } + + return this.modifier.withType(int.class).read(1); + } + + /** + * Gets the wrapped value of this data watcher item. + * + * @return The wrapped value + */ + public Object getValue() { + return getWrapped(this.getRawValue()); + } + + /** + * Sets the value of this item. + * + * @param value New value + */ + public void setValue(Object value) { + this.setValue(value, false); + } + + /** + * Gets the raw value of this data watcher item. + * + * @return Raw value + */ + public Object getRawValue() { + if (VALUE_INDEX == null) { + VALUE_INDEX = MinecraftReflection.watcherObjectExists() ? 1 : 2; + } + + return this.modifier.readSafely(VALUE_INDEX); + } + + /** + * Sets the value of this item. + * + * @param value New value + * @param updateClient Whether to update the client + */ + public void setValue(Object value, boolean updateClient) { + if (VALUE_INDEX == null) { + VALUE_INDEX = MinecraftReflection.watcherObjectExists() ? 1 : 2; + } + + this.modifier.write(VALUE_INDEX, getUnwrapped(value)); + if (updateClient) { + this.setDirtyState(true); + } + } + + /** + * Whether the value must be synchronized with the client. + * + * @return True if it must, false if not + */ + public boolean getDirtyState() { + return this.modifier.withType(boolean.class).read(0); + } + + /** + * Sets this item's dirty state + * + * @param dirty New state + */ + public void setDirtyState(boolean dirty) { + this.modifier.withType(boolean.class).write(0, dirty); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (obj instanceof WrappedWatchableObject) { + WrappedWatchableObject that = (WrappedWatchableObject) obj; + return this.getIndex() == that.getIndex() && + this.getRawValue().equals(that.getRawValue()) && + this.getDirtyState() == that.getDirtyState(); + } + + return false; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + this.getIndex(); + result = prime * result + this.getRawValue().hashCode(); + result = prime * result + (this.getDirtyState() ? 1231 : 1237); + return result; + } + + @Override + public String toString() { + return "DataWatcherItem[index=" + this.getIndex() + ", value=" + this.getValue() + ", dirty=" + this.getDirtyState() + + "]"; + } } diff --git a/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java b/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java index 6db6ad9f..490fcb8a 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java +++ b/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java @@ -549,7 +549,7 @@ public class NbtFactory { * @param params - the parameters. */ private static Method findCreateMethod(Class base, Class... params) { - Method method = FuzzyReflection.fromClass(base, true).getMethodByParameters("createTag", base, params); + Method method = FuzzyReflection.fromClass(base, true).getMethodByReturnTypeAndParameters("createTag", base, params); method.setAccessible(true); return method; } diff --git a/src/main/java/com/comphenix/protocol/wrappers/nbt/TileEntityAccessor.java b/src/main/java/com/comphenix/protocol/wrappers/nbt/TileEntityAccessor.java index 76a35323..de8cd4bc 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/nbt/TileEntityAccessor.java +++ b/src/main/java/com/comphenix/protocol/wrappers/nbt/TileEntityAccessor.java @@ -1,13 +1,5 @@ package com.comphenix.protocol.wrappers.nbt; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; @@ -18,22 +10,28 @@ import com.comphenix.protocol.utility.ByteBuddyFactory; import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; - +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.implementation.InvocationHandlerAdapter; import net.bytebuddy.jar.asm.ClassReader; import net.bytebuddy.jar.asm.ClassVisitor; import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.jar.asm.Opcodes; +import net.bytebuddy.jar.asm.Type; import net.bytebuddy.matcher.ElementMatchers; - import org.bukkit.block.BlockState; /** * Manipulate tile entities. + * * @author Kristian */ class TileEntityAccessor { + private static final boolean BLOCK_DATA_INCL = MinecraftVersion.NETHER_UPDATE.atOrAbove() && !MinecraftVersion.CAVES_CLIFFS_1.atOrAbove(); @@ -45,7 +43,7 @@ class TileEntityAccessor { /** * Cached field accessors - {@link #EMPTY_ACCESSOR} represents no valid tile entity. */ - private static final ConcurrentMap, TileEntityAccessor> cachedAccessors = new ConcurrentHashMap<>(); + private static final Map, TileEntityAccessor> cachedAccessors = new HashMap<>(); private static Constructor nbtCompoundParserConstructor; @@ -59,8 +57,9 @@ class TileEntityAccessor { /** * Construct a new tile entity accessor. + * * @param tileEntityField - the tile entity field. - * @param state - the block state. + * @param state - the block state. */ private TileEntityAccessor(FieldAccessor tileEntityField, T state) { if (tileEntityField != null) { @@ -70,6 +69,40 @@ class TileEntityAccessor { } } + /** + * Retrieve an accessor for the tile entity at a specific location. + * + * @param state - the block state. + * @return The accessor, or NULL if this block state doesn't contain any tile entities. + */ + @SuppressWarnings("unchecked") + public static TileEntityAccessor getAccessor(T state) { + Class craftBlockState = state.getClass(); + TileEntityAccessor accessor = cachedAccessors.get(craftBlockState); + + // Attempt to construct the accessor + if (accessor == null) { + TileEntityAccessor created = null; + FieldAccessor field = null; + + try { + field = Accessors.getFieldAccessor(craftBlockState, MinecraftReflection.getTileEntityClass(), true); + } catch (Exception e) { + created = EMPTY_ACCESSOR; + } + if (field != null) { + created = new TileEntityAccessor(field, state); + } + accessor = cachedAccessors.putIfAbsent(craftBlockState, created); + + // We won the race + if (accessor == null) { + accessor = created; + } + } + return (TileEntityAccessor) (accessor != EMPTY_ACCESSOR ? accessor : null); + } + void findMethods(Class type, T state) { if (BLOCK_DATA_INCL) { Class tileEntityClass = MinecraftReflection.getTileEntityClass(); @@ -96,67 +129,72 @@ class TileEntityAccessor { // Possible read/write methods try { findMethodsUsingASM(); - } catch (IOException ex1) { - try { - // Much slower though - findMethodUsingByteBuddy(state); - } catch (Exception ex2) { - throw new RuntimeException("Cannot find read/write methods in " + type, ex2); - } + } catch (IOException exception) { + throw new RuntimeException("Cannot find read/write methods in " + type, exception); } // Ensure we found them - if (readCompound == null) + if (readCompound == null) { throw new RuntimeException("Unable to find read method in " + type); - if (writeCompound == null) + } + if (writeCompound == null) { throw new RuntimeException("Unable to find write method in " + type); + } } /** * Find the read/write methods in TileEntity. + * * @throws IOException If we cannot find these methods. */ private void findMethodsUsingASM() throws IOException { - final Class nbtCompoundClass = MinecraftReflection.getNBTCompoundClass(); - final Class tileEntityClass = MinecraftReflection.getTileEntityClass(); + Class tileEntityClass = MinecraftReflection.getTileEntityClass(); + Class nbtCompoundClass = MinecraftReflection.getNBTCompoundClass(); + + // the expected method descriptor (NBTTagCompound): Any + String tagCompoundName = Type.getInternalName(nbtCompoundClass); + String expectedDesc = "(L" + tagCompoundName + ";)"; + + // parse the tile entity class final ClassReader reader = new ClassReader(tileEntityClass.getCanonicalName()); - - final String tagCompoundName = getJarName(MinecraftReflection.getNBTCompoundClass()); - final String expectedDesc = "(L" + tagCompoundName + ";)"; - - reader.accept(new ClassVisitor(Opcodes.ASM5) { + reader.accept(new ClassVisitor(Opcodes.ASM9) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - final String methodName = name; - // Detect read/write calls to NBTTagCompound if (desc.startsWith(expectedDesc)) { - return new MethodVisitor(Opcodes.ASM5) { + return new MethodVisitor(Opcodes.ASM9) { + // keep track of the amount of read/write calls to NBTTagCompound private int readMethods; private int writeMethods; @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean intf) { + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean isInterface) { // This must be a virtual call on NBTTagCompound that accepts a String if (opcode == Opcodes.INVOKEVIRTUAL && tagCompoundName.equals(owner) && desc.startsWith("(Ljava/lang/String")) { - - // Is this a write call? + // write calls return nothing, read calls do if (desc.endsWith(")V")) { - writeMethods++; + this.writeMethods++; } else { - readMethods++; + this.readMethods++; } } } @Override public void visitEnd() { - if (readMethods > writeMethods) { - readCompound = Accessors.getMethodAccessor(tileEntityClass, methodName, nbtCompoundClass); - } else if (writeMethods > readMethods) { - writeCompound = Accessors.getMethodAccessor(tileEntityClass, methodName, nbtCompoundClass); + // more reads than writes? that is probably the read method then + if (this.readMethods > this.writeMethods) { + TileEntityAccessor.this.readCompound = Accessors.getMethodAccessor( + tileEntityClass, + name, + nbtCompoundClass); + } else if (this.writeMethods > this.readMethods) { + TileEntityAccessor.this.writeCompound = Accessors.getMethodAccessor( + tileEntityClass, + name, + nbtCompoundClass); } super.visitEnd(); @@ -169,75 +207,9 @@ class TileEntityAccessor { }, 0); } - private static Constructor setupNBTCompoundParserConstructor() - { - final Class nbtCompoundClass = MinecraftReflection.getNBTCompoundClass(); - try { - return ByteBuddyFactory.getInstance() - .createSubclass(nbtCompoundClass) - .name(MinecraftMethods.class.getPackage().getName() + ".NBTInvocationHandler") - .method(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class))) - .intercept(InvocationHandlerAdapter.of((obj, method, args) -> { - // If true, we've found a write method. Otherwise, a read method. - if (method.getReturnType().equals(Void.TYPE)) - throw new WriteMethodException(); - throw new ReadMethodException(); - })) - .make() - .load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION) - .getLoaded() - .getDeclaredConstructor(); - } catch (NoSuchMethodException e) { - throw new RuntimeException("Failed to find NBTCompound constructor."); - } - } - - /** - * Find the read/write methods in TileEntity. - * @param blockState - the block state. - * @throws IOException If we cannot find these methods. - */ - private void findMethodUsingByteBuddy(T blockState) throws IllegalAccessException, InvocationTargetException, - InstantiationException { - if (nbtCompoundParserConstructor == null) - nbtCompoundParserConstructor = setupNBTCompoundParserConstructor(); - - final Class nbtCompoundClass = MinecraftReflection.getNBTCompoundClass(); - - final Object compound = nbtCompoundParserConstructor.newInstance(); - final Object tileEntity = tileEntityField.get(blockState); - - // Look in every read/write like method - for (Method method : FuzzyReflection.fromObject(tileEntity, true). - getMethodListByParameters(Void.TYPE, new Class[] { nbtCompoundClass })) { - - try { - method.invoke(tileEntity, compound); - } catch (InvocationTargetException e) { - if (e.getCause() instanceof ReadMethodException) { - readCompound = Accessors.getMethodAccessor(method, true); - } else if (e.getCause() instanceof WriteMethodException) { - writeCompound = Accessors.getMethodAccessor(method, true); - } else { - // throw new RuntimeException("Inner exception.", e); - } - } catch (Exception e) { - throw new RuntimeException("Generic reflection error.", e); - } - } - } - - /** - * Retrieve the JAR name (slash instead of dots) of the given class. - * @param clazz - the class. - * @return The JAR name. - */ - private static String getJarName(Class clazz) { - return clazz.getCanonicalName().replace('.', '/'); - } - /** * Read the NBT compound that represents a given tile entity. + * * @param state - tile entity represented by a block state. * @return The compound. */ @@ -252,7 +224,8 @@ class TileEntityAccessor { /** * Write the NBT compound as a tile entity. - * @param state - target block state. + * + * @param state - target block state. * @param compound - the compound. */ public void writeBlockState(T state, NbtCompound compound) { @@ -266,61 +239,4 @@ class TileEntityAccessor { readCompound.invoke(tileEntity, NbtFactory.fromBase(compound).getHandle()); } } - - /** - * Retrieve an accessor for the tile entity at a specific location. - * @param state - the block state. - * @return The accessor, or NULL if this block state doesn't contain any tile entities. - */ - @SuppressWarnings("unchecked") - public static TileEntityAccessor getAccessor(T state) { - Class craftBlockState = state.getClass(); - TileEntityAccessor accessor = cachedAccessors.get(craftBlockState); - - // Attempt to construct the accessor - if (accessor == null ) { - TileEntityAccessor created = null; - FieldAccessor field = null; - - try { - field = Accessors.getFieldAccessor(craftBlockState, MinecraftReflection.getTileEntityClass(), true); - } catch (Exception e) { - created = EMPTY_ACCESSOR; - } - if (field != null) { - created = new TileEntityAccessor(field, state); - } - accessor = cachedAccessors.putIfAbsent(craftBlockState, created); - - // We won the race - if (accessor == null) { - accessor = created; - } - } - return (TileEntityAccessor) (accessor != EMPTY_ACCESSOR ? accessor : null); - } - - /** - * An internal exception used to detect read methods. - * @author Kristian - */ - private static class ReadMethodException extends RuntimeException { - private static final long serialVersionUID = 1L; - - public ReadMethodException() { - super("A read method was executed."); - } - } - - /** - * An internal exception used to detect write methods. - * @author Kristian - */ - private static class WriteMethodException extends RuntimeException { - private static final long serialVersionUID = 1L; - - public WriteMethodException() { - super("A write method was executed."); - } - } } diff --git a/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java b/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java index 88602313..cdeab428 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java +++ b/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java @@ -141,7 +141,7 @@ class WrappedElement implements NbtWrapper { if (methodGetTypeID == null) { // Use the base class methodGetTypeID = FuzzyReflection.fromClass(MinecraftReflection.getNBTBaseClass()). - getMethodByParameters("getTypeID", byte.class, new Class[0]); + getMethodByReturnTypeAndParameters("getTypeID", byte.class, new Class[0]); } if (type == null) { try { @@ -205,7 +205,7 @@ class WrappedElement implements NbtWrapper { // Use the base class methodClone = FuzzyReflection.fromClass(base). - getMethodByParameters("clone", base, new Class[0]); + getMethodByReturnTypeAndParameters("clone", base, new Class[0]); } try { diff --git a/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java b/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java index d93ff004..68cb223a 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java +++ b/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java @@ -1,9 +1,5 @@ package com.comphenix.protocol.wrappers.nbt.io; -import java.io.DataInput; -import java.io.DataOutput; -import java.lang.reflect.Method; - import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; @@ -14,156 +10,119 @@ import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.comphenix.protocol.wrappers.nbt.NbtList; import com.comphenix.protocol.wrappers.nbt.NbtWrapper; +import java.io.DataInput; +import java.io.DataOutput; +import java.lang.reflect.Method; public class NbtBinarySerializer { - private static final Class NBT_BASE_CLASS = MinecraftReflection.getNBTBaseClass(); - - private interface LoadMethod { - /** - * Load an NBT compound from a given stream. - * @param input - the input stream. - * @return The loaded NBT compound. - */ - public abstract Object loadNbt(DataInput input); - } - - /** - * Load an NBT compound from the NBTBase static method pre-1.7.2. - */ - private static class LoadMethodNbtClass implements LoadMethod { - private MethodAccessor accessor = getNbtLoadMethod(DataInput.class); - - @Override - public Object loadNbt(DataInput input) { - return accessor.invoke(null, input); - } - } - - /** - * Load an NBT compound from the NBTCompressedStreamTools static method in 1.7.2 - 1.7.5 - */ - private static class LoadMethodWorldUpdate implements LoadMethod { - private MethodAccessor accessor = getNbtLoadMethod(DataInput.class, int.class); - - @Override - public Object loadNbt(DataInput input) { - return accessor.invoke(null, input, 0); - } - } - /** - * Load an NBT compound from the NBTCompressedStreamTools static method in 1.7.8 - */ - private static class LoadMethodSkinUpdate implements LoadMethod { - private Class readLimitClass = MinecraftReflection.getNBTReadLimiterClass(); - private Object readLimiter = FuzzyReflection.fromClass(readLimitClass).getSingleton(); - private MethodAccessor accessor = getNbtLoadMethod(DataInput.class, int.class, readLimitClass); - - @Override - public Object loadNbt(DataInput input) { - return accessor.invoke(null, input, 0, readLimiter); - } - } - - // Used to read and write NBT - private static Method methodWrite; - - /** - * Method selected for loading NBT compounds. - */ - private static LoadMethod loadMethod; - /** * Retrieve a default instance of the NBT binary serializer. */ public static final NbtBinarySerializer DEFAULT = new NbtBinarySerializer(); - + private static final Class NBT_BASE_CLASS = MinecraftReflection.getNBTBaseClass(); + + // Used to read and write NBT + private static MethodAccessor methodWrite; + + /** + * Method selected for loading NBT compounds. + */ + private static LoadMethod loadMethod; + + private static MethodAccessor getNbtLoadMethod(Class... parameters) { + Method method = getUtilityClass().getMethodByReturnTypeAndParameters("load", NBT_BASE_CLASS, parameters); + return Accessors.getMethodAccessor(method); + } + + private static FuzzyReflection getUtilityClass() { + return FuzzyReflection.fromClass(MinecraftReflection.getNbtCompressedStreamToolsClass(), true); + } + /** * Write the content of a wrapped NBT tag to a stream. - * @param Type - * @param value - the NBT tag to write. + * + * @param Type + * @param value - the NBT tag to write. * @param destination - the destination stream. */ - public void serialize(NbtBase value, DataOutput destination) { + public void serialize(NbtBase value, DataOutput destination) { if (methodWrite == null) { Class base = MinecraftReflection.getNBTBaseClass(); - - // Use the base class - methodWrite = getUtilityClass(). - getMethodByParameters("writeNBT", base, DataOutput.class); - methodWrite.setAccessible(true); - } - - try { - methodWrite.invoke(null, NbtFactory.fromBase(value).getHandle(), destination); - } catch (Exception e) { - throw new FieldAccessException("Unable to write NBT " + value, e); + Method writeNBT = getUtilityClass().getMethodByParameters("writeNBT", base, DataOutput.class); + + methodWrite = Accessors.getMethodAccessor(writeNBT); } + + methodWrite.invoke(null, NbtFactory.fromBase(value).getHandle(), destination); } /** * Load an NBT tag from a stream. + * * @param Type - * @param source - the input stream. + * @param source - the input stream. * @return An NBT tag. */ public NbtWrapper deserialize(DataInput source) { - LoadMethod method = loadMethod; - if (loadMethod == null) { - if (MinecraftReflection.isUsingNetty()) { - try { - method = new LoadMethodWorldUpdate(); - } catch (IllegalArgumentException e) { - // Cannot find that method - must be in 1.7.8 - method = new LoadMethodSkinUpdate(); - } - } else { - method = new LoadMethodNbtClass(); - } - // Save the selected method - loadMethod = method; + loadMethod = new LoadMethodSkinUpdate(); } - + try { - return NbtFactory.fromNMS(method.loadNbt(source), null); + return NbtFactory.fromNMS(loadMethod.loadNbt(source), null); } catch (Exception e) { throw new FieldAccessException("Unable to read NBT from " + source, e); } } - - private static MethodAccessor getNbtLoadMethod(Class... parameters) { - return Accessors.getMethodAccessor(getUtilityClass().getMethodByParameters("load", NBT_BASE_CLASS, parameters), true); - } - - private static FuzzyReflection getUtilityClass() { - if (MinecraftReflection.isUsingNetty()) { - return FuzzyReflection.fromClass(MinecraftReflection.getNbtCompressedStreamToolsClass(), true); - } else { - return FuzzyReflection.fromClass(MinecraftReflection.getNBTBaseClass(), true); - } - } - + /** * Load an NBT compound from a stream. + * * @param source - the input stream. * @return An NBT compound. */ - @SuppressWarnings("rawtypes") public NbtCompound deserializeCompound(DataInput source) { // I always seem to override generics ... - return (NbtCompound) (NbtBase) deserialize(source); + return (NbtCompound) (NbtBase) this.deserialize(source); } - + /** * Load an NBT list from a stream. - * @param Type + * + * @param Type * @param source - the input stream. * @return An NBT list. */ - @SuppressWarnings({"rawtypes", "unchecked"}) + @SuppressWarnings("unchecked") public NbtList deserializeList(DataInput source) { - return (NbtList) (NbtBase) deserialize(source); + return (NbtList) (NbtBase) this.deserialize(source); + } + + private interface LoadMethod { + + /** + * Load an NBT compound from a given stream. + * + * @param input - the input stream. + * @return The loaded NBT compound. + */ + Object loadNbt(DataInput input); + } + + /** + * Load an NBT compound from the NBTCompressedStreamTools static method since 1.7. + */ + private static class LoadMethodSkinUpdate implements LoadMethod { + + private final Class readLimitClass = MinecraftReflection.getNBTReadLimiterClass(); + private final Object readLimiter = FuzzyReflection.fromClass(this.readLimitClass).getSingleton(); + private final MethodAccessor accessor = getNbtLoadMethod(DataInput.class, int.class, this.readLimitClass); + + @Override + public Object loadNbt(DataInput input) { + return this.accessor.invoke(null, input, 0, this.readLimiter); + } } } diff --git a/src/test/java/com/comphenix/integration/protocol/SimpleCraftBukkitITCase.java b/src/test/java/com/comphenix/integration/protocol/SimpleCraftBukkitITCase.java index 6c00297d..06311773 100644 --- a/src/test/java/com/comphenix/integration/protocol/SimpleCraftBukkitITCase.java +++ b/src/test/java/com/comphenix/integration/protocol/SimpleCraftBukkitITCase.java @@ -4,7 +4,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.ExactReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; import com.google.common.collect.Lists; import com.google.common.io.Files; import java.io.File; @@ -60,7 +61,7 @@ public class SimpleCraftBukkitITCase { FAKE_PLUGIN = createPlugin("FakeTestPluginIntegration"); // No need to look for updates - FieldUtils.writeStaticField(ProtocolLibrary.class, "UPDATES_DISABLED", Boolean.TRUE, true); + ProtocolLibrary.disableUpdates(); // Wait until the server and all the plugins have loaded Bukkit.getScheduler().callSyncMethod(FAKE_PLUGIN, new Callable() { @@ -141,15 +142,17 @@ public class SimpleCraftBukkitITCase { @SuppressWarnings("unchecked") private static void initializePlugin(Plugin plugin) { PluginManager manager = Bukkit.getPluginManager(); + ExactReflection reflect = ExactReflection.fromObject(manager, true); try { - List plugins = (List) FieldUtils.readField(manager, "plugins", true); - Map lookupNames = (Map) FieldUtils.readField(manager, "lookupNames", true); + List plugins = (List) Accessors.getFieldAccessor(reflect.getField("plugins")).get(manager); + Map lookupNames = (Map) Accessors + .getFieldAccessor(reflect.getField("lookupNames")) + .get(manager); - /// Associate this plugin + // Associate this plugin plugins.add(plugin); lookupNames.put(plugin.getName(), plugin); - } catch (Exception e) { throw new RuntimeException("Unable to access the fields of " + manager, e); } diff --git a/src/test/java/com/comphenix/protocol/BukkitInitialization.java b/src/test/java/com/comphenix/protocol/BukkitInitialization.java index 04040a02..cf9e31ab 100644 --- a/src/test/java/com/comphenix/protocol/BukkitInitialization.java +++ b/src/test/java/com/comphenix/protocol/BukkitInitialization.java @@ -3,8 +3,9 @@ package com.comphenix.protocol; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.comphenix.protocol.reflect.FieldUtils; -import com.comphenix.protocol.utility.Constants; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.utility.MinecraftReflectionTestUtil; import java.util.Collections; import java.util.List; import net.minecraft.SharedConstants; @@ -88,7 +89,8 @@ public class BukkitInitialization { SpigotWorldConfig mockWorldConfig = mock(SpigotWorldConfig.class); try { - FieldUtils.writeField(nmsWorld.getClass().getField("spigotConfig"), nmsWorld, mockWorldConfig, true); + FieldAccessor spigotConfig = Accessors.getFieldAccessor(nmsWorld.getClass().getField("spigotConfig")); + spigotConfig.set(nmsWorld, mockWorldConfig); } catch (ReflectiveOperationException ex) { throw new RuntimeException(ex); } @@ -118,7 +120,7 @@ public class BukkitInitialization { ex.printStackTrace(); } - Constants.init(); + MinecraftReflectionTestUtil.init(); } } } \ No newline at end of file diff --git a/src/test/java/com/comphenix/protocol/PacketTypeTest.java b/src/test/java/com/comphenix/protocol/PacketTypeTest.java index 82d011bf..ace2adaf 100644 --- a/src/test/java/com/comphenix/protocol/PacketTypeTest.java +++ b/src/test/java/com/comphenix/protocol/PacketTypeTest.java @@ -22,7 +22,7 @@ import com.comphenix.protocol.PacketType.Protocol; import com.comphenix.protocol.PacketType.Sender; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.injector.packet.PacketRegistry; -import com.comphenix.protocol.utility.Constants; +import com.comphenix.protocol.utility.MinecraftReflectionTestUtil; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashSet; @@ -62,7 +62,7 @@ public class PacketTypeTest { @SuppressWarnings("unchecked") public static void main(String[] args) throws Exception { - Constants.init(); + MinecraftReflectionTestUtil.init(); Set> allTypes = new HashSet<>(); List> newTypes = new ArrayList<>(); diff --git a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index 28217a20..9839fd33 100644 --- a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -20,7 +20,6 @@ import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual; import static com.comphenix.protocol.utility.TestUtils.equivalentItem; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; @@ -88,13 +87,13 @@ import org.apache.commons.lang.SerializationUtils; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.Sound; -import org.bukkit.WorldType; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.util.Vector; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -263,25 +262,6 @@ public class PacketContainerTest { assertItemCollectionsEqual(items, comparison); } - @Test - public void testGetWorldTypeModifier() { - // Not used in Netty - if (MinecraftReflection.isUsingNetty()) { - return; - } - - PacketContainer loginPacket = new PacketContainer(PacketType.Play.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 testGetNbtModifier() { PacketContainer updateTileEntity = new PacketContainer(PacketType.Play.Server.TILE_ENTITY_DATA); @@ -747,13 +727,9 @@ public class PacketContainerTest { @Test public void testCloning() { - boolean failed = false; - // Try constructing all the packets for (PacketType type : PacketType.values()) { - if (type.isDeprecated() || type.name().contains("CUSTOM_PAYLOAD") || type.name().contains("TAGS") - || !type.isSupported() - || type == PacketType.Play.Server.RECIPES) { + if (type.isDeprecated() || !type.isSupported() || type.name().contains("CUSTOM_PAYLOAD")) { continue; } @@ -766,21 +742,29 @@ public class PacketContainerTest { // Make sure watchable collections can be cloned if (type == PacketType.Play.Server.ENTITY_METADATA) { constructed.getWatchableCollectionModifier().write(0, Lists.newArrayList( - new WrappedWatchableObject(new WrappedDataWatcherObject(0, Registry.get(Byte.class)), + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.get(Byte.class)), (byte) 1), - new WrappedWatchableObject(new WrappedDataWatcherObject(0, Registry.get(String.class)), + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.get(String.class)), "String"), - new WrappedWatchableObject(new WrappedDataWatcherObject(0, Registry.get(Float.class)), 1.0F), - new WrappedWatchableObject(new WrappedDataWatcherObject(0, Registry.getChatComponentSerializer(true)), + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.get(Float.class)), + 1.0F), + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.getChatComponentSerializer(true)), Optional.of(ComponentConverter.fromBaseComponent(TEST_COMPONENT).getHandle())), - new WrappedWatchableObject(new WrappedDataWatcherObject(0, Registry.get(VillagerData.class)), + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.get(VillagerData.class)), new VillagerData(VillagerType.b, VillagerProfession.c, 69)) )); } else if (type == PacketType.Play.Server.CHAT) { constructed.getChatComponents().write(0, ComponentConverter.fromBaseComponent(TEST_COMPONENT)); - //constructed.getModifier().write(1, TEST_COMPONENT); } + // gives some indication which cloning process fails as the checks itself are happening outside this method + System.out.println("Cloning " + type); + // Clone the packet both ways PacketContainer shallowCloned = constructed.shallowClone(); this.assertPacketsEqual(constructed, shallowCloned); @@ -788,12 +772,9 @@ public class PacketContainerTest { PacketContainer deepCloned = constructed.deepClone(); this.assertPacketsEqual(constructed, deepCloned); } catch (Exception ex) { - ex.printStackTrace(); - failed = true; + Assertions.fail("Unable to clone " + type, ex); } } - - assertFalse(failed, "Packet(s) failed to clone"); } // Convert to objects that support equals() @@ -875,7 +856,7 @@ public class PacketContainerTest { Set fields = FuzzyReflection.fromObject(a, true).getFields(); for (Field field : fields) { if (!Modifier.isStatic(field.getModifiers())) { - FieldAccessor accessor = Accessors.getFieldAccessor(field, true); + FieldAccessor accessor = Accessors.getFieldAccessor(field); testEquality(accessor.get(a), accessor.get(b)); } } diff --git a/src/test/java/com/comphenix/protocol/reflect/accessors/AccessorsTest.java b/src/test/java/com/comphenix/protocol/reflect/accessors/AccessorsTest.java index d8cc176c..c91bd4da 100644 --- a/src/test/java/com/comphenix/protocol/reflect/accessors/AccessorsTest.java +++ b/src/test/java/com/comphenix/protocol/reflect/accessors/AccessorsTest.java @@ -1,7 +1,10 @@ package com.comphenix.protocol.reflect.accessors; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import com.comphenix.protocol.reflect.ExactReflection; +import java.lang.reflect.Field; import org.junit.jupiter.api.Test; public class AccessorsTest { @@ -10,9 +13,13 @@ public class AccessorsTest { public void testField() { Player player = new Player(123, "ABC"); - Accessors.getFieldAccessor(player.getClass(), "id", true).set(player, 0); - Accessors.getFieldAccessor(player.getClass(), "name", true).set(player, "MODIFIED"); - assertEquals(0, player.getId()); + Field id = assertDoesNotThrow(() -> ExactReflection.fromClass(Player.class, true).getField("id")); + Field name = assertDoesNotThrow(() -> ExactReflection.fromClass(Player.class, true).getField("name")); + + assertDoesNotThrow(() -> Accessors.getFieldAccessor(id).set(player, 15)); + assertDoesNotThrow(() -> Accessors.getFieldAccessor(name).set(player, "MODIFIED")); + + assertEquals(15, player.getId()); assertEquals("MODIFIED", player.getName()); } @@ -20,10 +27,19 @@ public class AccessorsTest { public void testMethod() { Player player = new Player(123, "ABC"); - Accessors.getMethodAccessor(player.getClass(), "setId", int.class).invoke(player, 0); + assertDoesNotThrow(() -> Accessors.getMethodAccessor(player.getClass(), "setId", int.class).invoke(player, 0)); assertEquals(0, player.getId()); } + @Test + public void testConstructor() { + Player player = (Player) assertDoesNotThrow(() -> Accessors + .getConstructorAccessor(Player.class, int.class, String.class) + .invoke(12, "hi")); + assertEquals(12, player.getId()); + assertEquals("hi", player.getName()); + } + // --- Some classes we can use for testing --- private static class Entity { diff --git a/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTestUtil.java b/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTestUtil.java new file mode 100644 index 00000000..01ded076 --- /dev/null +++ b/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTestUtil.java @@ -0,0 +1,14 @@ +package com.comphenix.protocol.utility; + +public class MinecraftReflectionTestUtil { + + public static final String PACKAGE_VERSION = "v1_19_R1"; + public static final String NMS = "net.minecraft"; + public static final String OBC = "org.bukkit.craftbukkit." + PACKAGE_VERSION; + public static final MinecraftVersion CURRENT_VERSION = MinecraftVersion.WILD_UPDATE; + + public static void init() { + MinecraftReflection.setMinecraftPackage(NMS, OBC); + MinecraftVersion.setCurrentVersion(CURRENT_VERSION); + } +} diff --git a/src/test/java/com/comphenix/protocol/utility/TestUtils.java b/src/test/java/com/comphenix/protocol/utility/TestUtils.java index 12359ba2..c2865597 100644 --- a/src/test/java/com/comphenix/protocol/utility/TestUtils.java +++ b/src/test/java/com/comphenix/protocol/utility/TestUtils.java @@ -47,6 +47,6 @@ public class TestUtils { } public static void setFinalField(Object obj, Field field, Object newValue) { - Accessors.getFieldAccessor(field, true).set(obj, newValue); + Accessors.getFieldAccessor(field).set(obj, newValue); } } diff --git a/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java b/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java index 9f3cfb7a..249de783 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java @@ -54,4 +54,4 @@ public class EnumWrappersTest { public void testValidity() { assertEquals(EnumWrappers.INVALID, KNOWN_INVALID); } -} +} \ No newline at end of file