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:
parent
7f0bc7fd24
commit
c5f0550953
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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 + "]";
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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() + "\" }";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 + " }";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -6,4 +6,5 @@ package com.comphenix.protocol.utility;
|
|||
* @author Pim
|
||||
*/
|
||||
public interface ByteBuddyGenerated {
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) { }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}));
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 + "]";
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
));
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue