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
This commit is contained in:
Pasqual Koschmieder 2022-07-24 16:16:05 +02:00 committed by GitHub
parent 7f0bc7fd24
commit c5f0550953
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
106 changed files with 5112 additions and 11659 deletions

View File

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

View File

@ -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<PacketTyp
/**
* Protocol version of all the current IDs.
*/
private static final MinecraftVersion PROTOCOL_VERSION = Constants.CURRENT_VERSION;
private static final MinecraftVersion PROTOCOL_VERSION = MinecraftVersion.LATEST;
private final Protocol protocol;
private final Sender sender;

View File

@ -41,7 +41,6 @@ public class ProtocolConfig {
private static final String METRICS_ENABLED = "metrics";
private static final String IGNORE_VERSION_CHECK = "ignore version check";
private static final String BACKGROUND_COMPILER_ENABLED = "background compiler";
private static final String DEBUG_MODE_ENABLED = "debug";
private static final String DETAILED_ERROR = "detailed error";
@ -307,15 +306,6 @@ public class ProtocolConfig {
return getGlobalValue(METRICS_ENABLED, true);
}
/**
* Retrieve whether or not the background compiler for structure modifiers is enabled or not.
*
* @return TRUE if it is enabled, FALSE otherwise.
*/
public boolean isBackgroundCompilerEnabled() {
return getGlobalValue(BACKGROUND_COMPILER_ENABLED, true);
}
/**
* Retrieve the last time we updated, in seconds since 1970.01.01 00:00.
*

View File

@ -25,13 +25,11 @@ import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.InternalManager;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.updater.Updater;
import com.comphenix.protocol.updater.Updater.UpdateType;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.NettyVersion;
import com.comphenix.protocol.utility.Util;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
@ -91,8 +89,6 @@ public class ProtocolLib extends JavaPlugin {
private static final int ASYNC_MANAGER_DELAY = 1;
private static final String PERMISSION_INFO = "protocol.info";
public static boolean UPDATES_DISABLED = false;
// these fields are only existing once, we can make them static
private static Logger logger;
private static ProtocolConfig config;
@ -101,7 +97,6 @@ public class ProtocolLib extends JavaPlugin {
private static ErrorReporter reporter = new BasicErrorReporter();
private Statistics statistics;
private BackgroundCompiler backgroundCompiler;
private int packetTask = -1;
private int tickCounter = 0;
@ -153,9 +148,6 @@ public class ProtocolLib extends JavaPlugin {
// Print the state of the debug mode
if (config.isDebug()) {
logger.warning("Debug mode is enabled!");
logger.info("Detected netty version: " + NettyVersion.getVersion());
} else {
NettyVersion.getVersion(); // this will cache the version
}
// And the state of the error reporter
@ -334,16 +326,6 @@ public class ProtocolLib extends JavaPlugin {
// Check for incompatible plugins
this.checkForIncompatibility(manager);
// Initialize background compiler
if (this.backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
this.backgroundCompiler = new BackgroundCompiler(this.getClassLoader(), reporter);
BackgroundCompiler.setInstance(this.backgroundCompiler);
logger.info("Started structure compiler thread.");
} else {
logger.info("Structure compiler thread has been disabled.");
}
// Set up command handlers
this.registerCommand(CommandProtocol.NAME, this.commandProtocol);
this.registerCommand(CommandPacket.NAME, this.commandPacket);
@ -518,7 +500,7 @@ public class ProtocolLib extends JavaPlugin {
ProtocolLib.this.updateConfiguration();
// Check for updates too
if (!UPDATES_DISABLED && (ProtocolLib.this.tickCounter % 20) == 0) {
if (!ProtocolLibrary.updatesDisabled() && (ProtocolLib.this.tickCounter % 20) == 0) {
ProtocolLib.this.checkUpdates();
}
}, ASYNC_MANAGER_DELAY, ASYNC_MANAGER_DELAY);
@ -560,7 +542,7 @@ public class ProtocolLib extends JavaPlugin {
}
} catch (Exception e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
UPDATES_DISABLED = true;
ProtocolLibrary.disableUpdates();
}
}
@ -583,13 +565,6 @@ public class ProtocolLib extends JavaPlugin {
logger.severe("╚══════════════════════════════════════════════════════════════════╝");
}
// Disable compiler
if (this.backgroundCompiler != null) {
this.backgroundCompiler.shutdownAll();
this.backgroundCompiler = null;
BackgroundCompiler.setInstance(null);
}
// Clean up
if (this.packetTask >= 0) {
this.getServer().getScheduler().cancelTask(this.packetTask);

View File

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

View File

@ -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<ChunkPosition> 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.
* <p>
* This modifier will automatically marshal between the visible ProtocolLib ChunkPosition and the
* internal Minecraft ChunkPosition.
*
* @return A modifier for ChunkPosition list fields.
*/
public StructureModifier<List<ChunkPosition>> 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.
* <p>

View File

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

View File

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

View File

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

View File

@ -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<PacketType> COMPILING = new HashSet<>();
// Structure modifiers
private static final Map<Class<?>, Supplier<Object>> PACKET_INSTANCE_CREATORS = new ConcurrentHashMap<>();
private static final Map<PacketType, StructureModifier<Object>> 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<Object> 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<Object> getStructure(Class<?> packetType) {
// Compile structures by default
return getStructure(packetType, true);
}
/**
* Retrieve a cached structure modifier given a packet type.
*
* @param packetType - packet type.
* @param compile - whether or not to asynchronously compile the structure modifier.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(Class<?> packetType, boolean compile) {
// Get the ID from the class
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<Object> getStructure(final PacketType packetType, boolean compile) {
public static StructureModifier<Object> getStructure(final PacketType packetType) {
Preconditions.checkNotNull(packetType, "type cannot be null");
StructureModifier<Object> 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;
}

View File

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

View File

@ -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() {

View File

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

View File

@ -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<Object> value = (List<Object>) accessor.get(serverConnection);
// mark down that we've overridden the field

View File

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

View File

@ -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<Map<Object, Map<Integer, Class<?>>>> maps = modifier.withTarget(protocol).withType(Map.class);

View File

@ -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.
* <p>
* 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<AsmMethod> 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<AsmMethod> getMethodCalls(Class<?> clazz, Method method) throws IOException {
final ClassReader reader = new ClassReader(clazz.getCanonicalName());
final List<AsmMethod> 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;
}
}

View File

@ -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 <T> The specific type.
* @author Kristian
*/
public interface EquivalentConverter<T> {
/**
* Retrieve a copy of the generic type from a specific type.
* <p>
* 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<T> {
* Retrieve a copy of the specific type using an instance of the generic type.
* <p>
* 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<T> {
/**
* Due to type erasure, we need to explicitly keep a reference to the specific type.
*
* @return The specific type.
*/
Class<T> getSpecificType();

View File

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

View File

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

View File

@ -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.
* <p>
* 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.
* <p>
* This constructor is public to permit tools that require a JavaBean
* instance to operate.
*/
public FieldUtils() {
super();
}
/**
* Gets an accessible <code>Field</code> 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 <code>Field</code> 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
* <code>setAccessible</code> method. <code>False</code> 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;
}
/**
* <p>Gets a <code>List</code> of all interfaces implemented by the given
* class and its superclasses.</p>
*
* <p>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.</p>
*
* @param cls the class to look up, may be <code>null</code>
* @return the <code>List</code> of interfaces in order,
* <code>null</code> if null input
*/
private static Set<Class> getAllInterfaces(Class cls) {
if (cls == null) return null;
final Set<Class> 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
* <code>setAccessible</code> 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
* <code>setAccessible</code> method. <code>False</code> 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
* <code>setAccessible</code> 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
* <code>setAccessible</code> method. <code>False</code> 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
* <code>setAccessible</code> method. <code>False</code> 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
* <code>setAccessible</code> method. <code>False</code> 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
* <code>setAccessible</code> method. <code>False</code> 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 <code>target</code> or
* <code>fieldName</code> 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
* <code>setAccessible</code> method. <code>False</code> will
* only match public fields.
* @throws IllegalArgumentException if <code>target</code> or
* <code>fieldName</code> 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;
}
}
}

View File

@ -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 <T> 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 <T> 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> T getFieldValue(Object instance, Class<T> 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 <T> Set<T> combineArrays(T[]... arrays) {
Set<T> 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.
* <p>
* 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<MethodInfo> matcher) {
List<Method> result = getMethodList(matcher);
List<Method> 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.
* <p>
* 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<MethodInfo> matcher, String preferred) {
List<Method> result = getMethodList(matcher);
List<Method> 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.
* <p>
* 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<Method> getMethodList(AbstractFuzzyMatcher<MethodInfo> matcher) {
final List<Method> 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<Method> methods = getMethodListByParameters(returnType, args);
List<Method> 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.
* <p>
* 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<Method> getMethodList(AbstractFuzzyMatcher<MethodInfo> matcher) {
// finds and adds all matching methods
List<Method> 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.
* <p>
* 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<Method> getMethodListByParameters(Class<?> returnType, Class<?>[] args) {
final List<Method> methods = new ArrayList<>();
public List<Method> getMethodListByParameters(Class<?> returnType, Class<?>... args) {
List<Method> 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<Field> fields = getFieldListByType(type);
if (fields.size() > 0) {
return fields.get(0);
} else {
// Looks like we're outdated. Too bad.
throw new IllegalArgumentException(String.format("Unable to find a field %s with the type %s in %s",
name, type.getName(), source.getName())
);
}
}
/**
* Retrieves every field with a type equal to or more specific to the given type.
* @param type - type of the fields to find.
* @return Every field with a type that is an instance of the given type.
*/
public List<Field> getFieldListByType(Class<?> type) {
final List<Field> 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.
* <p>
* 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<Field> matcher) {
List<Field> result = getFieldList(matcher);
if (result.size() > 0)
List<Field> 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<Field> 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<Field> getFieldListByType(Class<?> type) {
// Field with a compatible type
List<Field> 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.
* <p>
* 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<Field> getFieldList(AbstractFuzzyMatcher<Field> matcher) {
final List<Field> fields = new ArrayList<>();
// Add all matching fields to the list
for (Field field : getFields()) {
if (matcher.isMatch(field, source)) {
List<Field> fields = new ArrayList<>();
for (Field field : this.getFields()) {
if (matcher.isMatch(field, this.source)) {
fields.add(field);
}
}
return fields;
}
/**
* Retrieves a field by type.
* <p>
@ -487,199 +468,128 @@ public class FuzzyReflection {
* <li>java.util.List</li>
* <li>net.comphenix.xp.ExperienceMod</li>
* </ul>
*
* @param typeRegex - regular expression that will match the field type.
* @return The first field with a type that matches the given regular expression.
* @throws IllegalArgumentException If the field cannot be found.
*/
public Field getFieldByType(String typeRegex) {
Pattern match = Pattern.compile(typeRegex);
// Like above, only here we test the field type
for (Field field : getFields()) {
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.
* <p>
* Note that the type is matched using the full canonical representation, i.e.:
* <ul>
* <li>java.util.List</li>
* <li>net.comphenix.xp.ExperienceMod</li>
* </ul>
* @param typeRegex - regular expression that will match the field type.
* @param ignored - types to ignore.
* @return The first field with a type that matches the given regular expression.
* @throws IllegalArgumentException If the field cannot be found.
*/
@SuppressWarnings("rawtypes")
public Field getFieldByType(String typeRegex, Set<Class> ignored) {
Pattern match = Pattern.compile(typeRegex);
// Like above, only here we test the field type
for (Field field : getFields()) {
Class type = field.getType();
if (!ignored.contains(type) && match.matcher(type.getName()).matches()) {
return field;
}
}
// Looks like we're outdated. Too bad.
throw new IllegalArgumentException("Unable to find a field with the type " +
typeRegex + " in " + source.getName());
}
/**
* Retrieve the first constructor that matches.
* <p>
* 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<MethodInfo> matcher) {
final List<Constructor<?>> result = getConstructorList(matcher);
if (result.size() > 0)
List<Constructor<?>> 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.
* <p>
* 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<String, Method> getMappedMethods(List<Method> methods) {
final Map<String, Method> 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.
* <p>
* 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<Constructor<?>> getConstructorList(AbstractFuzzyMatcher<MethodInfo> matcher) {
final List<Constructor<?>> 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<Constructor<?>> 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.
* <p>
* Private, protected and package fields are ignored if forceAccess is FALSE.
*
* @return Every field.
*/
public Set<Field> 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<Field> 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<Field> 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).
* <p>
* Private, protected and package methods are ignored if forceAccess is FALSE.
*
* @return Every method.
*/
public Set<Method> getMethods() {
// We will only consider private methods in the declared class
if (forceAccess)
return setUnion(source.getDeclaredMethods(), source.getMethods());
else
return setUnion(source.getMethods());
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).
* <p>
* Private, protected and package constructors are ignored if forceAccess is FALSE.
*
* @return Every constructor.
*/
public Set<Constructor<?>> getConstructors() {
if (forceAccess)
return setUnion(source.getDeclaredConstructors());
else
return setUnion(source.getConstructors());
}
// Prevent duplicate fields
@SafeVarargs
private static <T> Set<T> setUnion(T[]... array) {
final Set<T> 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());
}
}

View File

@ -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<Integer, String> members = HashBiMap.create();
/**
* Registers every declared integer field.
*/
public IntEnum() {
registerAll();
}
/**
* Registers every public int field as a member.
*/
protected void registerAll() {
try {
// Register every int field
for (Field entry : this.getClass().getFields()) {
if (entry.getType().equals(int.class)) {
registerMember(entry.getInt(this), entry.getName());
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* Registers a member.
* @param id - id of member.
* @param name - name of member.
*/
protected void registerMember(int id, String name) {
members.put(id, name);
}
/**
* Determines whether or not the given member exists.
* @param id - the ID of the member to find.
* @return TRUE if a member with the given ID exists, FALSE otherwise.
*/
public boolean hasMember(int id) {
return members.containsKey(id);
}
/**
* Retrieve the ID of the member with the given name.
* @param name - name of member to retrieve.
* @return ID of the member, or NULL if not found.
*/
public Integer valueOf(String name) {
return members.inverse().get(name);
}
/**
* Retrieve the name of the member with the given id.
* @param id - id of the member to retrieve.
* @return Declared name of the member, or NULL if not found.
*/
public String getDeclaredName(Integer id) {
return members.get(id);
}
/**
* Retrieve the ID of every registered member.
* @return Enumeration of every value.
*/
public Set<Integer> values() {
return new HashSet<Integer>(members.keySet());
}
}

View File

@ -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 extends Annotation> T getAnnotation(Class<T> 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<MethodInfo> fromMethods(Collection<Method> methods) {
final List<MethodInfo> list = new ArrayList<>();
List<MethodInfo> 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 extends Annotation> T getAnnotation(Class<T> 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<MethodInfo> 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<MethodInfo> fromConstructors(Collection<Constructor<?>> constructors) {
final List<MethodInfo> infos = new ArrayList<>();
for (Constructor<?> constructor : constructors)
List<MethodInfo> 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.
* <p>
* 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();

File diff suppressed because it is too large Load Diff

View File

@ -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<Class, StructureModifier<Object>> cache =
new ConcurrentHashMap<Class, StructureModifier<Object>>();
private static final Map<Class<?>, StructureModifier<Object>> CACHE = new HashMap<>();
/**
* Retrieve a usable structure modifier for the given object type.
* <p>
* Will attempt to reuse any other structure modifiers we have cached.
*
* @param type - the type of the object we are modifying.
* @return A structure modifier for the given type.
*/
private StructureModifier<Object> getModifier(Class<?> type) {
Class<?> packetClass = MinecraftReflection.getPacketClass();
// Handle subclasses of the packet class with our custom structure cache
// 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<Object> modifier = cache.get(type);
// Create the structure modifier if we haven't already
StructureModifier<Object> modifier = CACHE.get(type);
if (modifier == null) {
StructureModifier<Object> value = new StructureModifier<Object>(type, null, false);
modifier = cache.putIfAbsent(type, value);
if (modifier == null)
StructureModifier<Object> 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.
* <p>
* 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<Object> modifierSource, StructureModifier<Object> modifierDest, int fieldIndex) {
protected void transformField(
StructureModifier<Object> modifierSource,
StructureModifier<Object> modifierDest,
int fieldIndex
) {
Object value = modifierSource.read(fieldIndex);
modifierDest.write(fieldIndex, value);
}
// Internal method that will actually implement the recursion
private void copyToInternal(Object source, Object destination, Class<?> commonType, boolean copyPublic) {
if (source == null)
throw new IllegalArgumentException("Source cannot be NULL");
if (destination == null)
throw new IllegalArgumentException("Destination cannot be NULL");
StructureModifier<Object> modifier = getModifier(commonType);
StructureModifier<Object> modifier = this.getModifier(commonType);
// Add target
StructureModifier<Object> modifierSource = modifier.withTarget(source);
StructureModifier<Object> modifierDest = modifier.withTarget(destination);
// Copy every field
try {
for (int i = 0; i < modifierSource.size(); i++) {
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);
}
}

View File

@ -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.
* <p>
* 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<Object> previous = new HashSet<Object>();
// 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<Object> previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException {
Set<Object> 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<Object, Object> map, Class<?> current, Class<?> stop,
Set<Object> previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException {
Set<Object> previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException {
boolean first = true;
output.append("[");
for (Entry<Object, Object> 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<Object> previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException {
Set<Object> 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<Object> previous, int hierachyIndex, boolean first,
ObjectPrinter printer) throws IllegalAccessException {
Set<Object> 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<Object> previous, int hierachyIndex, ObjectPrinter printer) throws IllegalAccessException {
Set<Object> 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<Object> previous, int hierachyIndex,
ObjectPrinter printer) throws IllegalAccessException {
Class<?> stop, Set<Object> 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<Object, Object>) 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.
* <p>
* 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);
}
}

View File

@ -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.
* <p>
* 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 + "]";
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Object> modifier = new StructureModifier<>(source.getClass(), true).withTarget(source);
StructureModifier<Object> modifier = new StructureModifier<>(source.getClass()).withTarget(source);
List<?> list = (List<?>) modifier.read(0);
Object empty = modifier.read(1);

View File

@ -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.
* <p>
* 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<StructureKey, List<CompileListener<?>>> 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.
* <p>
* 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<Class, StructureModifier> cache, final Class key) {
@SuppressWarnings("unchecked")
final StructureModifier<Object> uncompiled = cache.get(key);
if (uncompiled != null) {
scheduleCompilation(uncompiled, new CompileListener<Object>() {
@Override
public void onCompiled(StructureModifier<Object> compiledModifier) {
// Update cache
cache.put(key, compiledModifier);
}
});
}
}
/**
* Ensure that the given structure modifier is eventually compiled.
* @param <TKey> Type
* @param uncompiled - structure modifier to compile.
* @param listener - listener responsible for responding to the compilation.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> 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<Object>() {
@Override
public Object call() throws Exception {
StructureModifier<TKey> 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<TKey>) 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 <TKey> Type
* @param uncompiled - the structure modifier that may get compiled.
* @param listener - the listener to invoke in that case.
*/
@SuppressWarnings("unchecked")
public <TKey> void addListener(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> 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;
}
}

View File

@ -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("<init>", Type.VOID_TYPE, new Type[] {type}));
}
}
/**
* Generates the instruction to invoke a constructor.
*
* @param Type the class in which the constructor is defined.
* @param method the constructor to be invoked.
*/
public void invokeConstructor(final Type Type, final MethodDescriptor method){
invokeInsn(Opcodes.INVOKESPECIAL, Type, method);
}
/**
* Generates a DUP_X1 instruction.
*/
public void dupX1(){
mv.visitInsn(Opcodes.DUP_X1);
}
/**
* Generates a DUP_X2 instruction.
*/
public void dupX2(){
mv.visitInsn(Opcodes.DUP_X2);
}
/**
* Generates a POP instruction.
*/
public void pop(){
mv.visitInsn(Opcodes.POP);
}
/**
* Generates a SWAP instruction.
*/
public void swap(){
mv.visitInsn(Opcodes.SWAP);
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack.
*/
public void push(final boolean value){
push(value ? 1 : 0);
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack.
*/
public void push(final int value) {
if (value >= -1 && value <= 5) {
mv.visitInsn(Opcodes.ICONST_0 + value);
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
mv.visitIntInsn(Opcodes.BIPUSH, value);
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
mv.visitIntInsn(Opcodes.SIPUSH, value);
} else {
mv.visitLdcInsn(new Integer(value));
}
}
/**
* Generates the instruction to create a new object.
*
* @param Type the class of the object to be created.
*/
public void newInstance(final Type Type){
TypeInsn(Opcodes.NEW, Type);
}
/**
* Generates the instruction to push the given value on the stack.
*
* @param value the value to be pushed on the stack. May be <tt>null</tt>.
*/
public void push(final String value) {
if (value == null) {
mv.visitInsn(Opcodes.ACONST_NULL);
} else {
mv.visitLdcInsn(value);
}
}
/**
* Generates the instructions to unbox the top stack value. This value is
* replaced by its unboxed equivalent on top of the stack.
*
* @param type
* the Type of the top stack value.
*/
public void unbox(final Type type){
Type t = NUMBER_Type;
MethodDescriptor sig = null;
switch(type.getSort()) {
case Type.VOID:
return;
case Type.CHAR:
t = CHARACTER_Type;
sig = CHAR_VALUE;
break;
case Type.BOOLEAN:
t = BOOLEAN_Type;
sig = BOOLEAN_VALUE;
break;
case Type.DOUBLE:
sig = DOUBLE_VALUE;
break;
case Type.FLOAT:
sig = FLOAT_VALUE;
break;
case Type.LONG:
sig = LONG_VALUE;
break;
case Type.INT:
case Type.SHORT:
case Type.BYTE:
sig = INT_VALUE;
}
if(sig == null) {
checkCast(type);
} else {
checkCast(t);
invokeVirtual(t, sig);
}
}
/**
* Generates the instruction to check that the top stack value is of the
* given Type.
*
* @param Type a class or interface Type.
*/
public void checkCast(final Type Type){
if(!Type.equals(OBJECT_Type)) {
TypeInsn(Opcodes.CHECKCAST, Type);
}
}
/**
* Generates the instruction to invoke a normal method.
*
* @param owner the class in which the method is defined.
* @param method the method to be invoked.
*/
public void invokeVirtual(final Type owner, final MethodDescriptor method){
invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method);
}
/**
* Generates an invoke method instruction.
*
* @param opcode the instruction's opcode.
* @param type the class in which the method is defined.
* @param method the method to be invoked.
*/
private void invokeInsn(final int opcode, final Type type, final MethodDescriptor method){
String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName();
mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor());
}
/**
* Generates a Type dependent instruction.
*
* @param opcode the instruction's opcode.
* @param type the instruction's operand.
*/
private void TypeInsn(final int opcode, final Type type){
String desc;
if(type.getSort() == Type.ARRAY) {
desc = type.getDescriptor();
} else {
desc = type.getInternalName();
}
mv.visitTypeInsn(opcode, desc);
}
}

View File

@ -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 <TKey> - type of the structure modifier field.
*/
public interface CompileListener<TKey> {
/**
* Invoked when a structure modifier has been successfully compiled.
* @param compiledModifier - the compiled structure modifier.
*/
public void onCompiled(StructureModifier<TKey> compiledModifier);
}

View File

@ -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<Object> {
// Used to compile instances of structure modifiers
protected StructureCompiler compiler;
// Fields that originally were read only
private Set<Integer> 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<Object> writeDefaults() throws FieldAccessException {
DefaultInstances generator = DefaultInstances.DEFAULT;
// Write a default instance to every field
for (Map.Entry<Field, Integer> entry : defaultFields.entrySet()) {
Integer index = entry.getValue();
Field field = entry.getKey();
// 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<Object> 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<Object> writeGenerated(int index, Object value) throws FieldAccessException;
@Override
public StructureModifier<Object> withTarget(Object target) {
if (compiler != null)
return compiler.compile(super.withTarget(target));
else
return super.withTarget(target);
}
}

View File

@ -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<String, String> DESCRIPTORS;
static {
DESCRIPTORS = new HashMap<String, String>();
DESCRIPTORS.put("void", "V");
DESCRIPTORS.put("byte", "B");
DESCRIPTORS.put("char", "C");
DESCRIPTORS.put("double", "D");
DESCRIPTORS.put("float", "F");
DESCRIPTORS.put("int", "I");
DESCRIPTORS.put("long", "J");
DESCRIPTORS.put("short", "S");
DESCRIPTORS.put("boolean", "Z");
}
/**
* Creates a new {@link 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 <code>method</code> 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 <code>method</code> could not get
* parsed.
*/
public static MethodDescriptor getMethod(
final String method,
final boolean defaultPackage) throws IllegalArgumentException
{
int space = method.indexOf(' ');
int start = method.indexOf('(', space) + 1;
int end = method.indexOf(')', start);
if (space == -1 || start == -1 || end == -1) {
throw new IllegalArgumentException();
}
String returnType = method.substring(0, space);
String methodName = method.substring(space + 1, start - 1).trim();
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();
}
}

View File

@ -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<TField> extends CompiledStructureModifier<TField> {
//
// private Packet20NamedEntitySpawn typedTarget;
//
// public CompiledStructureModifierPacket20(StructureModifier<TField> other, StructureCompiler compiler) {
// super();
// initialize(other);
// this.target = other.getTarget();
// this.typedTarget = (Packet20NamedEntitySpawn) target;
// this.compiler = compiler;
// }
//
// @Override
// protected Object readGenerated(int fieldIndex) throws FieldAccessException {
//
// Packet20NamedEntitySpawn target = typedTarget;
//
// switch (fieldIndex) {
// case 0: return (Object) target.a;
// case 1: return (Object) target.b;
// case 2: return (Object) target.c;
// case 3: return super.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<TField> writeGenerated(int index, Object value) throws FieldAccessException {
//
// Packet20NamedEntitySpawn target = typedTarget;
//
// switch (index) {
// case 0: target.a = (Integer) value; break;
// case 1: target.b = (String) value; break;
// case 2: target.c = (Integer) value; break;
// case 3: target.d = (Integer) value; break;
// case 4: super.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<StructureKey, Class> compiledCache = new ConcurrentHashMap<StructureKey, Class>();
// The class loader we'll store our classes
private ClassLoader loader;
// References to other classes
private static String PACKAGE_NAME = "com/comphenix/protocol/reflect/compiler";
private static String SUPER_CLASS = "com/comphenix/protocol/reflect/StructureModifier";
private static String COMPILED_CLASS = PACKAGE_NAME + "/CompiledStructureModifier";
private static String FIELD_EXCEPTION_CLASS = "com/comphenix/protocol/reflect/FieldAccessException";
// 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 <TField> Type
* @param source - the structure modifier to look up.
* @return TRUE if we successfully found a previously generated class, FALSE otherwise.
*/
public <TField> boolean lookupClassLoader(StructureModifier<TField> source) {
StructureKey key = new StructureKey(source);
// See if there's a need to lookup the class name
if (compiledCache.containsKey(key)) {
return true;
}
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.
* <p>
* WARNING: Do NOT call this method in the main thread. Compiling may easily take 10 ms, which is already
* over 1/4 of a tick (50 ms). Let the background thread automatically compile the structure modifiers instead.
* @param <TField> Type
* @param source - structure modifier to compile.
* @return A compiled structure modifier.
*/
@SuppressWarnings("unchecked")
public synchronized <TField> StructureModifier<TField> compile(StructureModifier<TField> source) {
// We cannot optimize a structure modifier with no public fields
if (!isAnyPublic(source.getFields())) {
return source;
}
StructureKey key = new StructureKey(source);
Class<?> compiledClass = compiledCache.get(key);
if (!compiledCache.containsKey(key)) {
compiledClass = generateClass(source);
compiledCache.put(key, compiledClass);
}
// Next, create an instance of this class
try {
return (StructureModifier<TField>) compiledClass.getConstructor(
StructureModifier.class, StructureCompiler.class).
newInstance(source, this);
} catch (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 <TField> Class<?> generateClass(StructureModifier<TField> 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<Field> 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<Field> fields, String targetSignature, String targetName) {
String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";";
String methodSignature = "(ILjava/lang/Object;)L" + SUPER_CLASS + "<Ljava/lang/Object;>;";
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", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(Opcodes.ATHROW);
mv.visitLabel(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<Field> fields, String targetSignature, String targetName) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null,
new String[] { "com/comphenix/protocol/reflect/FieldAccessException" });
BoxingHelper boxingHelper = new BoxingHelper(mv);
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", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(Opcodes.ATHROW);
mv.visitMaxs(5, 3);
mv.visitEnd();
}
private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
"(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V",
"(L" + SUPER_CLASS + "<Ljava/lang/Object;>;L" + PACKAGE_NAME + "/StructureCompiler;)V", null);
String fullClassName = PACKAGE_NAME + "/" + className;
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "<init>", "()V");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 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();
}
}

View File

@ -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.
* <p>
* This class should ideally never expose mutable state. Its round number must be immutable.
*
* @author Kristian
*/
public abstract class AbstractFuzzyMatcher<T> implements Comparable<AbstractFuzzyMatcher<T>> {
private Integer roundNumber;
@FunctionalInterface
public interface AbstractFuzzyMatcher<T> {
/**
* 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.
* <p>
* Matchers with a lower round number are applied before matchers with a higher round number.
* <p>
* 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.
* <p>
* 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<T> 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<T> inverted() {
return new AbstractFuzzyMatcher<T>() {
@Override
public boolean isMatch(T value, Object parent) {
return !AbstractFuzzyMatcher.this.isMatch(value, parent);
}
@Override
protected int calculateRoundNumber() {
return -2;
}
};
default AbstractFuzzyMatcher<T> 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<T> and(final AbstractFuzzyMatcher<T> other) {
return new AbstractFuzzyMatcher<T>() {
@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<T> and(final AbstractFuzzyMatcher<T> 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<T> or(final AbstractFuzzyMatcher<T> other) {
return new AbstractFuzzyMatcher<T>() {
@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<T> or(final AbstractFuzzyMatcher<T> other) {
// Either can be true
return (value, parent) -> this.isMatch(value, parent) || other.isMatch(value, parent);
}
}

View File

@ -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 <T> - type that it matches.
* @author Kristian
*/
public abstract class AbstractFuzzyMember<T extends Member> extends AbstractFuzzyMatcher<T> {
public abstract class AbstractFuzzyMember<T extends Member> implements AbstractFuzzyMatcher<T> {
// Accessibility matchers
protected int modifiersRequired;
protected int modifiersBanned;
protected Pattern nameRegex;
protected AbstractFuzzyMatcher<Class<?>> declaringMatcher = ClassExactMatcher.MATCH_ALL;
protected AbstractFuzzyMatcher<Class<?>> 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<T extends AbstractFuzzyMember<?>> {
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<T> requireModifier(int modifier) {
member.modifiersRequired |= modifier;
return this;
}
/**
* Require that every matching member is public.
* @return This builder, for chaining.
*/
public Builder<T> 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<T> 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<T> 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<T> nameRegex(Pattern pattern) {
member.nameRegex = pattern;
return this;
}
/**
* Set the exact name of the member we are matching.
* <p>
* This will overwrite the regular expression rule.
* @param name - exact name.
* @return This builder, for chaining.
*/
public Builder<T> 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<T> 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<T> 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<T> 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<T> declaringClassMatching(AbstractFuzzyMatcher<Class<?>> 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.
* <p>
* 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.
* <p>
* Use this to prepare any special values.
*/
protected void prepareBuild() {
// No need to prepare anything
}
// Clone a given contract
protected AbstractFuzzyMember(AbstractFuzzyMember<T> other) {
this.modifiersRequired = other.modifiersRequired;
@ -174,131 +41,279 @@ public abstract class AbstractFuzzyMember<T extends Member> 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.
* <p>
* 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<Class<?>> 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.
* <p>
* Used by {@link #toString()} to print a representation of this object.
*
* @return A modifiable key-value view.
*/
protected Map<String, Object> getKeyValueView() {
final Map<String, Object> map = new LinkedHashMap<>();
Map<String, Object> 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<T> other = (AbstractFuzzyMember<T>) 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<T extends AbstractFuzzyMember<?>> {
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<T> requireModifier(int modifier) {
this.member.modifiersRequired |= modifier;
return this;
}
/**
* Require that every matching member is public.
*
* @return This builder, for chaining.
*/
public Builder<T> 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<T> 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<T> 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<T> nameRegex(Pattern pattern) {
this.member.nameRegex = pattern;
return this;
}
/**
* Set the exact name of the member we are matching.
* <p>
* This will overwrite the regular expression rule.
*
* @param name - exact name.
* @return This builder, for chaining.
*/
public Builder<T> 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<T> 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<T> 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<T> 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<T> declaringClassMatching(AbstractFuzzyMatcher<Class<?>> 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.
* <p>
* Builders should call {@link AbstractFuzzyMember#prepareBuild()} when constructing new objects.
*
* @return New instance of this type.
*/
public abstract T build();
}
}

View File

@ -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<Class<?>> {
/**
* 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.
* <p>
* 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.
* <p>
* 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;
}
}

View File

@ -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<Class<?>> {
final class ClassRegexMatcher implements AbstractFuzzyMatcher<Class<?>> {
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() + "\" }";
}
}

View File

@ -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<Class<?>> {
final class ClassSetMatcher implements AbstractFuzzyMatcher<Class<?>> {
private final Set<Class<?>> classes;
public ClassSetMatcher(Set<Class<?>> 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 + " }";
}
}

View File

@ -0,0 +1,114 @@
package com.comphenix.protocol.reflect.fuzzy;
/**
* Used to check class equality.
*
* @author Kristian
*/
final class ClassTypeMatcher implements AbstractFuzzyMatcher<Class<?>> {
/**
* 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.
* <p>
* 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
}
}

View File

@ -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<Class<?>> {
public class FuzzyClassContract implements AbstractFuzzyMatcher<Class<?>> {
private final ImmutableList<AbstractFuzzyMatcher<Field>> fieldContracts;
private final ImmutableList<AbstractFuzzyMatcher<MethodInfo>> methodContracts;
private final ImmutableList<AbstractFuzzyMatcher<MethodInfo>> constructorContracts;
private final ImmutableList<AbstractFuzzyMatcher<Class<?>>> baseclassContracts;
private final ImmutableList<AbstractFuzzyMatcher<Class<?>>> interfaceContracts;
/**
* Represents a class contract builder.
* @author Kristian
*
*/
public static class Builder {
private final List<AbstractFuzzyMatcher<Field>> fieldContracts = new ArrayList<>();
private final List<AbstractFuzzyMatcher<MethodInfo>> methodContracts = new ArrayList<>();
private final List<AbstractFuzzyMatcher<MethodInfo>> constructorContracts = new ArrayList<>();
private final List<AbstractFuzzyMatcher<Class<?>>> baseclassContracts = new ArrayList<>();
private final List<AbstractFuzzyMatcher<Class<?>>> interfaceContracts = new ArrayList<>();
/**
* Add a new field contract.
* @param matcher - new field contract.
* @return This builder, for chaining.
*/
public Builder field(AbstractFuzzyMatcher<Field> 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<MethodInfo> 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<MethodInfo> 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<Class<?>> 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<Class<?>> 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<Class<?>> {
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.
* <p>
* This list is ordered in descending order of priority.
*
* @return List of every field contract.
*/
public ImmutableList<AbstractFuzzyMatcher<Field>> getFieldContracts() {
return fieldContracts;
return this.fieldContracts;
}
/**
* Retrieve an immutable list of every method contract.
* <p>
* This list is ordered in descending order of priority.
*
* @return List of every method contract.
*/
public ImmutableList<AbstractFuzzyMatcher<MethodInfo>> getMethodContracts() {
return methodContracts;
return this.methodContracts;
}
/**
* Retrieve an immutable list of every constructor contract.
* <p>
* This list is ordered in descending order of priority.
*
* @return List of every constructor contract.
*/
public ImmutableList<AbstractFuzzyMatcher<MethodInfo>> getConstructorContracts() {
return constructorContracts;
return this.constructorContracts;
}
/**
* Retrieve an immutable list of every baseclass contract.
* <p>
* This list is ordered in descending order of priority.
*
* @return List of every baseclass contract.
*/
public ImmutableList<AbstractFuzzyMatcher<Class<?>>> getBaseclassContracts() {
return baseclassContracts;
return this.baseclassContracts;
}
/**
* Retrieve an immutable list of every interface contract.
* <p>
* This list is ordered in descending order of priority.
*
* @return List of every interface contract.
*/
public ImmutableList<AbstractFuzzyMatcher<Class<?>>> 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 <T> int findHighestRound(Collection<AbstractFuzzyMatcher<T>> list) {
int highest = 0;
// Go through all the elements
for (AbstractFuzzyMatcher<T> 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 <T> boolean processContracts(Collection<T> values, Object parent, List<AbstractFuzzyMatcher<T>> 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 <T> boolean processValue(T value, Object parent, List<AbstractFuzzyMatcher<T>> matchers) {
for (int i = 0; i < matchers.size(); i++) {
if (matchers.get(i).isMatch(value, parent)) {
return true;
}
}
// No match
return false;
}
private <T> int processValue(T value, Object parent, boolean accepted[], List<AbstractFuzzyMatcher<T>> matchers) {
// The order matters
for (int i = 0; i < matchers.size(); i++) {
if (!accepted[i]) {
AbstractFuzzyMatcher<T> 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 <T> boolean processValue(T value, Object parent, List<AbstractFuzzyMatcher<T>> matchers) {
// check if all given contracts match the given value
for (AbstractFuzzyMatcher<T> matcher : matchers) {
if (!matcher.isMatch(value, parent)) {
return false;
}
}
// they all match
return true;
}
@Override
public String toString() {
final Map<String, Object> 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<AbstractFuzzyMatcher<Field>> fieldContracts = new ArrayList<>();
private final List<AbstractFuzzyMatcher<MethodInfo>> methodContracts = new ArrayList<>();
private final List<AbstractFuzzyMatcher<MethodInfo>> constructorContracts = new ArrayList<>();
private final List<AbstractFuzzyMatcher<Class<?>>> baseclassContracts = new ArrayList<>();
private final List<AbstractFuzzyMatcher<Class<?>>> interfaceContracts = new ArrayList<>();
/**
* Add a new field contract.
*
* @param matcher - new field contract.
* @return This builder, for chaining.
*/
public Builder field(AbstractFuzzyMatcher<Field> 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<MethodInfo> 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<MethodInfo> 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<Class<?>> 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<Class<?>> 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}";
}
}

View File

@ -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<Field> {
private AbstractFuzzyMatcher<Class<?>> typeMatcher = ClassExactMatcher.MATCH_ALL;
/**
* Represents a builder for a field matcher.
*
* @author Kristian
*/
public static class Builder extends AbstractFuzzyMember.Builder<FuzzyFieldContract> {
@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<Class<?>> 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<Class<?>> matcher) {
member.typeMatcher = matcher;
return this;
}
@Override
public FuzzyFieldContract build() {
member.prepareBuild();
return new FuzzyFieldContract(member);
}
}
private AbstractFuzzyMatcher<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> 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<String, Object> getKeyValueView() {
Map<String, Object> 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<FuzzyFieldContract> {
@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<Class<?>> 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<Class<?>> matcher) {
this.member.typeMatcher = matcher;
return this;
}
@Override
public FuzzyFieldContract build() {
this.member.prepareBuild();
return new FuzzyFieldContract(this.member);
}
return true;
}
}

View File

@ -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<Class<?>> MATCH_ALL = new AbstractFuzzyMatcher<Class<?>>() {
@Override
public boolean isMatch(Class<?> value, Object parent) {
return true;
}
@Override
protected int calculateRoundNumber() {
return 0;
}
};
private static final AbstractFuzzyMatcher<Class<?>> 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<Class<?>> matchArray(@Nonnull final AbstractFuzzyMatcher<Class<?>> componentMatcher) {
Preconditions.checkNotNull(componentMatcher, "componentMatcher cannot be NULL.");
return new AbstractFuzzyMatcher<Class<?>>() {
@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<Class<?>> matchArray(AbstractFuzzyMatcher<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> matchParent() {
return new AbstractFuzzyMatcher<Class<?>>() {
@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<Class<?>> 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<Class<?>> matchRegex(String regex) {
return FuzzyMatchers.matchRegex(Pattern.compile(regex));
}
/**
* Determine if two patterns are the same.
* <p>
* 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());
}
}
}

View File

@ -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<MethodInfo> {
private static class ParameterClassMatcher extends AbstractFuzzyMatcher<Class<?>[]> {
// Match return value
private AbstractFuzzyMatcher<Class<?>> returnMatcher = ClassTypeMatcher.MATCH_ALL;
// Handle parameters and exceptions
private List<ParameterClassMatcher> paramMatchers;
private List<ParameterClassMatcher> 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<Class<?>> getReturnMatcher() {
return this.returnMatcher;
}
/**
* Retrieve an immutable list of every parameter matcher for this method.
*
* @return Immutable list of every parameter matcher.
*/
public ImmutableList<ParameterClassMatcher> getParamMatchers() {
if (this.paramMatchers instanceof ImmutableList) {
return (ImmutableList<ParameterClassMatcher>) 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<ParameterClassMatcher> 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<ParameterClassMatcher> 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<ParameterClassMatcher> 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<String, Object> getKeyValueView() {
Map<String, Object> 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<Class<?>[]> {
/**
* The expected index.
*/
private final AbstractFuzzyMatcher<Class<?>> typeMatcher;
private final Integer indexMatch;
/**
* Construct a new parameter class matcher.
*
* @param typeMatcher - class type matcher.
*/
public ParameterClassMatcher(@Nonnull AbstractFuzzyMatcher<Class<?>> 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<Class<?>> 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<Class<?>> returnMatcher = ClassExactMatcher.MATCH_ALL;
// Handle parameters and exceptions
private List<ParameterClassMatcher> paramMatchers;
private List<ParameterClassMatcher> exceptionMatchers;
// Expected parameter count
private Integer paramCount;
/**
* Represents a builder for a fuzzy method contract.
*
*
* @author Kristian
*/
public static class Builder extends AbstractFuzzyMember.Builder<FuzzyMethodContract> {
public static final class Builder extends AbstractFuzzyMember.Builder<FuzzyMethodContract> {
@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<Class<?>> 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.
* <p>
* 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.
* <p>
* 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<Class<?>> 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.
* <p>
* 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.
* <p>
* 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<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> 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<MethodInfo> {
@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<Class<?>> getReturnMatcher() {
return returnMatcher;
}
/**
* Retrieve an immutable list of every parameter matcher for this method.
* @return Immutable list of every parameter matcher.
*/
public ImmutableList<ParameterClassMatcher> getParamMatchers() {
if (paramMatchers instanceof ImmutableList)
return (ImmutableList<ParameterClassMatcher>) 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<ParameterClassMatcher> 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<ParameterClassMatcher> 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<ParameterClassMatcher> 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<String, Object> getKeyValueView() {
Map<String, Object> 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;
}
}

View File

@ -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<Class<?>, 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<Node> 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.
* <p>
@ -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.
* <p>
@ -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<Class<?>> 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();

View File

@ -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<Class<?>, 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<Class<?>, 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;
};
}

View File

@ -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 <T> DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional<T> createSubclass(Class<T> clz)
{
public <T> DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional<T> createSubclass(Class<T> 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 <T> DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional<T> createSubclass(Class<T> clz,
ConstructorStrategy.Default constructorStrategy)
{
public <T> DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional<T> createSubclass(
Class<T> clz,
ConstructorStrategy.Default constructorStrategy
) {
return new ByteBuddy()
.subclass(clz, constructorStrategy)
.implement(ByteBuddyGenerated.class);

View File

@ -6,4 +6,5 @@ package com.comphenix.protocol.utility;
* @author Pim
*/
public interface ByteBuddyGenerated {
}

View File

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

View File

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

View File

@ -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
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<String, Optional<Class<?>>> cache;
final class CachedPackage {
private final String packageName;
private final ClassSource source;
private final Map<String, Optional<Class<?>>> 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<Class<?>> 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;
}
}

View File

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

View File

@ -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<String, Class<?>> 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<String, Class<?>> 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.<String, Class<?>>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 <b>not</b> be returned, instead a {@code ClassNotFoundException} exception should be thrown.
* @return The corresponding class. If the class is not found, NULL should <b>not</b> 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;
}

View File

@ -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
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<Closeable> 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 extends Closeable> 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) { }
}
}

View File

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

View File

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

View File

@ -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<String, Method> netServer = getMethodList(
serverHandlerClass, MinecraftReflection.getPacketClass());
Map<String, Method> 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.
* <p>
* 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.
* <p>
* 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<String, Method> 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.
* <p>
* 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.
* <p>
* 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<Method> 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<Method> 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.");
}

View File

@ -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<MinecraftVersion, Integer> lookup = createLookup();
public final class MinecraftProtocolVersion {
private static final NavigableMap<MinecraftVersion, Integer> LOOKUP = createLookup();
private static NavigableMap<MinecraftVersion, Integer> createLookup() {
TreeMap<MinecraftVersion, Integer> 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<MinecraftVersion, Integer> result = lookup.floorEntry(version);
Entry<MinecraftVersion, Integer> result = LOOKUP.floorEntry(version);
return result != null ? result.getValue() : Integer.MIN_VALUE;
}
}

View File

@ -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<MinecraftVersion>, Serializable {
private static final long serialVersionUID = 1L;
public final class MinecraftVersion implements Comparable<MinecraftVersion>, 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<MinecraftVersion>, 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<MinecraftVersion>, 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<MinecraftVersion>, 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<MinecraftVersion>, 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<MinecraftVersion>, 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<MinecraftVersion>, 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<MinecraftVersion>, 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());
}
}

View File

@ -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<String, Version> 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;
}
}

View File

@ -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 <T> The type of the object to reconstruct.
* @author Pim
*/
public class ObjectReconstructor<T> {
private final Class<T> clz;
private final Field[] fields;
private final Constructor<?> ctor;
public ObjectReconstructor(final Class<T> 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);
}
}
}

View File

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

View File

@ -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<SnapshotVersion>, 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<SnapshotVersion>, Serializabl
throw new IllegalArgumentException("Cannot parse " + version + " as a snapshot version.");
}
}
/**
* Retrieve the snapshot date parser.
* <p>
* 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<SnapshotVersion>, 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();
}
}

View File

@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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}.
* <p>
* 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.
* <p>
* To supply a byte array, wrap it in a {@link java.io.ByteArrayInputStream ByteArrayInputStream}
* and {@link java.io.DataInputStream DataInputStream}.
*
* @param input - the target input stream.
* @return The resulting item stack, 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;
}
}

View File

@ -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 <E> List<E> asList(E... elements) {
List<E> 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;
}
/**

File diff suppressed because it is too large Load Diff

View File

@ -137,14 +137,14 @@ public class AutoWrapper<T> implements EquivalentConverter<T> {
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);
}
}

View File

@ -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<Integer> ints = mobEffectModifier.withTarget(generic).withType(int.class);
StructureModifier<Boolean> 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<Double> 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}));
}

View File

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

View File

@ -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<Integer> 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<ChunkPosition> getConverter() {
return new EquivalentConverter<ChunkPosition>() {
@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<Integer> 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<ChunkPosition> 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 + "]";
}
}

View File

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

View File

@ -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<Integer, Integer> noEntryTransform) {
return new ReadOnlyFieldAccessor() {
public static FieldAccessor wrapMapField(final FieldAccessor accessor, final Function<Integer, Integer> 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<Integer, Integer> 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.

View File

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

View File

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

View File

@ -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<WrappedChunkCoordinate> {
/**
* 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<Integer> SHARED_MODIFIER;
// The current modifier
private StructureModifier<Integer> 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<Integer> getModifier() {
if (SHARED_MODIFIER == null)
SHARED_MODIFIER = new StructureModifier<Object>(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<Object>) handle).compareTo(other.handle);
}
@Override
public String toString() {
return String.format("ChunkCoordinate [x: %s, y: %s, z: %s]", getX(), getY(), getZ());
}
}

View File

@ -328,16 +328,6 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
return (ItemStack) getObject(index);
}
/**
* Get a watched string.
*
* @param index - index of the watched string.
* @return The watched string, or NULL if this value doesn't exist.
*/
public WrappedChunkCoordinate getChunkCoordinate(int index) {
return (WrappedChunkCoordinate) getObject(index);
}
/**
* Retrieve a watchable object by index.
*
@ -748,7 +738,7 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
public Serializer getSerializer() {
if (getSerializer == null) {
getSerializer = Accessors.getMethodAccessor(FuzzyReflection.fromClass(HANDLE_TYPE, true)
.getMethodByParameters("getSerializer", MinecraftReflection.getDataWatcherSerializerClass(), new Class[0]));
.getMethodByReturnTypeAndParameters("getSerializer", MinecraftReflection.getDataWatcherSerializerClass(), new Class[0]));
}
Object serializer = getSerializer.invoke(handle);

View File

@ -34,16 +34,16 @@ public class WrappedGameProfile extends AbstractWrapper {
private static final ConstructorAccessor CREATE_UUID_STRING = Accessors.getConstructorAccessorOrNull(
GAME_PROFILE, UUID.class, String.class);
private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAcccessorOrNull(
private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAccessorOrNull(
GAME_PROFILE, "id", String.class);
private static final MethodAccessor GET_ID = Accessors.getMethodAcccessorOrNull(
private static final MethodAccessor GET_ID = Accessors.getMethodAccessorOrNull(
GAME_PROFILE, "getId");
private static final MethodAccessor GET_NAME = Accessors.getMethodAcccessorOrNull(
private static final MethodAccessor GET_NAME = Accessors.getMethodAccessorOrNull(
GAME_PROFILE, "getName");
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAcccessorOrNull(
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAccessorOrNull(
GAME_PROFILE, "getProperties");
private static final MethodAccessor IS_COMPLETE = Accessors.getMethodAcccessorOrNull(
private static final MethodAccessor IS_COMPLETE = Accessors.getMethodAccessorOrNull(
GAME_PROFILE, "isComplete");
// Fetching game profile

View File

@ -66,7 +66,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
private static Class<?> 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
));

View File

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

View File

@ -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
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<Object> 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<Object>(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.
* <p>
* 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.<Integer>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.<Boolean>withType(boolean.class).read(0);
}
/**
* Sets this item's dirty state
* @param dirty New state
*/
public void setDirtyState(boolean dirty) {
modifier.<Boolean>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.<Integer>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.<Boolean>withType(boolean.class).read(0);
}
/**
* Sets this item's dirty state
*
* @param dirty New state
*/
public void setDirtyState(boolean dirty) {
this.modifier.<Boolean>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()
+ "]";
}
}

View File

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

View File

@ -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<T extends BlockState> {
private static final boolean BLOCK_DATA_INCL = MinecraftVersion.NETHER_UPDATE.atOrAbove()
&& !MinecraftVersion.CAVES_CLIFFS_1.atOrAbove();
@ -45,7 +43,7 @@ class TileEntityAccessor<T extends BlockState> {
/**
* Cached field accessors - {@link #EMPTY_ACCESSOR} represents no valid tile entity.
*/
private static final ConcurrentMap<Class<?>, TileEntityAccessor<?>> cachedAccessors = new ConcurrentHashMap<>();
private static final Map<Class<?>, TileEntityAccessor<?>> cachedAccessors = new HashMap<>();
private static Constructor<?> nbtCompoundParserConstructor;
@ -59,8 +57,9 @@ class TileEntityAccessor<T extends BlockState> {
/**
* 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<T extends BlockState> {
}
}
/**
* 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 <T extends BlockState> TileEntityAccessor<T> 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<T>(field, state);
}
accessor = cachedAccessors.putIfAbsent(craftBlockState, created);
// We won the race
if (accessor == null) {
accessor = created;
}
}
return (TileEntityAccessor<T>) (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<T extends BlockState> {
// 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<T extends BlockState> {
}, 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<T extends BlockState> {
/**
* 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<T extends BlockState> {
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 <T extends BlockState> TileEntityAccessor<T> 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<T>(field, state);
}
accessor = cachedAccessors.putIfAbsent(craftBlockState, created);
// We won the race
if (accessor == null) {
accessor = created;
}
}
return (TileEntityAccessor<T>) (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.");
}
}
}

View File

@ -141,7 +141,7 @@ class WrappedElement<TType> implements NbtWrapper<TType> {
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<TType> implements NbtWrapper<TType> {
// Use the base class
methodClone = FuzzyReflection.fromClass(base).
getMethodByParameters("clone", base, new Class<?>[0]);
getMethodByReturnTypeAndParameters("clone", base, new Class<?>[0]);
}
try {

View File

@ -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 <TType> Type
* @param value - the NBT tag to write.
*
* @param <T> Type
* @param value - the NBT tag to write.
* @param destination - the destination stream.
*/
public <TType> void serialize(NbtBase<TType> value, DataOutput destination) {
public <T> void serialize(NbtBase<T> 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 <TType> Type
* @param source - the input stream.
* @param source - the input stream.
* @return An NBT tag.
*/
public <TType> NbtWrapper<TType> 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 <T> Type
*
* @param <T> Type
* @param source - the input stream.
* @return An NBT list.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@SuppressWarnings("unchecked")
public <T> NbtList<T> deserializeList(DataInput source) {
return (NbtList<T>) (NbtBase) deserialize(source);
return (NbtList<T>) (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);
}
}
}

View File

@ -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<Object>() {
@ -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<Plugin> plugins = (List<Plugin>) FieldUtils.readField(manager, "plugins", true);
Map<String, Plugin> lookupNames = (Map<String, Plugin>) FieldUtils.readField(manager, "lookupNames", true);
List<Object> plugins = (List<Object>) Accessors.getFieldAccessor(reflect.getField("plugins")).get(manager);
Map<String, Plugin> lookupNames = (Map<String, Plugin>) 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);
}

View File

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

Some files were not shown because too many files have changed in this diff Show More