Merge branch 'master' into 1661-Modifier_for_Either_and_SignatureData_in_PacketLoginInEncryptionBegin
This commit is contained in:
commit
1be3426a5d
|
@ -27,7 +27,6 @@ import com.comphenix.protocol.error.ErrorReporter;
|
|||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* A command to apply JavaScript filtering to the packet command.
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -21,7 +21,6 @@ import java.util.logging.Level;
|
|||
import java.util.logging.Logger;
|
||||
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
|
|
|
@ -121,16 +121,10 @@ public class AsyncListenerHandler {
|
|||
}
|
||||
|
||||
private void startWarningTask() {
|
||||
warningTask = filterManager.getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(AsyncListenerHandler.this, Report.
|
||||
newBuilder(REPORT_HANDLER_NOT_STARTED).
|
||||
messageParam(listener.getPlugin(), AsyncListenerHandler.this).
|
||||
build()
|
||||
);
|
||||
}
|
||||
}, 2 * TICKS_PER_SECOND);
|
||||
warningTask = filterManager.getScheduler().scheduleSyncDelayedTask(getPlugin(), () -> ProtocolLibrary.getErrorReporter().reportWarning(AsyncListenerHandler.this, Report.
|
||||
newBuilder(REPORT_HANDLER_NOT_STARTED).
|
||||
messageParam(listener.getPlugin(), AsyncListenerHandler.this).
|
||||
build()), 2 * TICKS_PER_SECOND);
|
||||
}
|
||||
|
||||
private void stopWarningTask() {
|
||||
|
@ -287,19 +281,16 @@ public class AsyncListenerHandler {
|
|||
final AsyncRunnable listenerLoop = getListenerLoop();
|
||||
|
||||
stopWarningTask();
|
||||
scheduleAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Thread thread = Thread.currentThread();
|
||||
|
||||
String previousName = thread.getName();
|
||||
String workerName = getFriendlyWorkerName(listenerLoop.getID());
|
||||
scheduleAsync(() -> {
|
||||
Thread thread = Thread.currentThread();
|
||||
|
||||
// Add the friendly worker name
|
||||
thread.setName(workerName);
|
||||
listenerLoop.run();
|
||||
thread.setName(previousName);
|
||||
}
|
||||
String previousName = thread.getName();
|
||||
String workerName = getFriendlyWorkerName(listenerLoop.getID());
|
||||
|
||||
// Add the friendly worker name
|
||||
thread.setName(workerName);
|
||||
listenerLoop.run();
|
||||
thread.setName(previousName);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -335,12 +326,7 @@ public class AsyncListenerHandler {
|
|||
final AsyncRunnable listenerLoop = getListenerLoop();
|
||||
final Function<AsyncRunnable, Void> delegateCopy = executor;
|
||||
|
||||
scheduleAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delegateCopy.apply(listenerLoop);
|
||||
}
|
||||
});
|
||||
scheduleAsync(() -> delegateCopy.apply(listenerLoop));
|
||||
}
|
||||
|
||||
private void scheduleAsync(Runnable runnable) {
|
||||
|
@ -423,31 +409,28 @@ public class AsyncListenerHandler {
|
|||
if (syncTask < 0) {
|
||||
stopWarningTask();
|
||||
|
||||
syncTask = filterManager.getScheduler().scheduleSyncRepeatingTask(getPlugin(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long stopTime = System.nanoTime() + unit.convert(time, TimeUnit.NANOSECONDS);
|
||||
|
||||
while (!cancelled) {
|
||||
PacketEvent packet = queuedPackets.poll();
|
||||
|
||||
if (packet == INTERUPT_PACKET || packet == WAKEUP_PACKET) {
|
||||
// Sorry, asynchronous threads!
|
||||
queuedPackets.add(packet);
|
||||
|
||||
// Try again next tick
|
||||
break;
|
||||
} else if (packet != null && packet.getAsyncMarker() != null) {
|
||||
processPacket(workerID, packet, "onSyncPacket()");
|
||||
} else {
|
||||
// No more packets left - wait a tick
|
||||
break;
|
||||
}
|
||||
|
||||
// Check time here, ensuring that we at least process one packet
|
||||
if (System.nanoTime() < stopTime)
|
||||
break;
|
||||
syncTask = filterManager.getScheduler().scheduleSyncRepeatingTask(getPlugin(), () -> {
|
||||
long stopTime = System.nanoTime() + unit.convert(time, TimeUnit.NANOSECONDS);
|
||||
|
||||
while (!cancelled) {
|
||||
PacketEvent packet = queuedPackets.poll();
|
||||
|
||||
if (packet == INTERUPT_PACKET || packet == WAKEUP_PACKET) {
|
||||
// Sorry, asynchronous threads!
|
||||
queuedPackets.add(packet);
|
||||
|
||||
// Try again next tick
|
||||
break;
|
||||
} else if (packet != null && packet.getAsyncMarker() != null) {
|
||||
processPacket(workerID, packet, "onSyncPacket()");
|
||||
} else {
|
||||
// No more packets left - wait a tick
|
||||
break;
|
||||
}
|
||||
|
||||
// Check time here, ensuring that we at least process one packet
|
||||
if (System.nanoTime() < stopTime)
|
||||
break;
|
||||
}
|
||||
}, tickDelay, tickDelay);
|
||||
|
||||
|
|
|
@ -17,6 +17,15 @@
|
|||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.comphenix.protocol.PacketStream;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLogger;
|
||||
|
@ -28,14 +37,6 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
|
|||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.primitives.Longs;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Contains information about the packet that is being processed by asynchronous listeners.
|
||||
|
|
|
@ -17,13 +17,9 @@
|
|||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import com.comphenix.protocol.events.*;
|
||||
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents a NO OPERATION listener.
|
||||
|
|
|
@ -17,15 +17,17 @@
|
|||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
|
@ -38,9 +40,9 @@ abstract class PacketSendingQueue {
|
|||
public static final int INITIAL_CAPACITY = 10;
|
||||
// Whether or not packet transmission must occur on a specific thread
|
||||
private final boolean notThreadSafe;
|
||||
private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
|
||||
private final PriorityBlockingQueue<PacketEventHolder> sendingQueue;
|
||||
// Asynchronous packet sending
|
||||
private Executor asynchronousSender;
|
||||
private final Executor asynchronousSender;
|
||||
// Whether or not we've run the cleanup procedure
|
||||
private boolean cleanedUp = false;
|
||||
|
||||
|
@ -50,7 +52,7 @@ abstract class PacketSendingQueue {
|
|||
* @param notThreadSafe - whether or not to synchronize with the main thread or a background thread.
|
||||
*/
|
||||
public PacketSendingQueue(boolean notThreadSafe, Executor asynchronousSender) {
|
||||
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
|
||||
this.sendingQueue = new PriorityBlockingQueue<>(INITIAL_CAPACITY);
|
||||
this.notThreadSafe = notThreadSafe;
|
||||
this.asynchronousSender = asynchronousSender;
|
||||
}
|
||||
|
@ -103,10 +105,10 @@ abstract class PacketSendingQueue {
|
|||
/***
|
||||
* Invoked when a list of packet IDs are no longer associated with any listeners.
|
||||
* @param packetsRemoved - packets that no longer have any listeners.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
* @param onMainThread - whether or not this is occurring on the main thread.
|
||||
*/
|
||||
public synchronized void signalPacketUpdate(List<PacketType> packetsRemoved, boolean onMainThread) {
|
||||
Set<PacketType> lookup = new HashSet<PacketType>(packetsRemoved);
|
||||
Set<PacketType> lookup = new HashSet<>(packetsRemoved);
|
||||
|
||||
// Note that this is O(n), so it might be expensive
|
||||
for (PacketEventHolder holder : sendingQueue) {
|
||||
|
@ -197,13 +199,8 @@ abstract class PacketSendingQueue {
|
|||
|
||||
// Let's give it what it wants
|
||||
if (onMainThread && wantAsync) {
|
||||
asynchronousSender.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// We know this isn't on the main thread
|
||||
processPacketHolder(false, holder);
|
||||
}
|
||||
});
|
||||
// We know this isn't on the main thread
|
||||
asynchronousSender.execute(() -> processPacketHolder(false, holder));
|
||||
|
||||
// Scheduler will do the rest
|
||||
return true;
|
||||
|
|
|
@ -24,8 +24,6 @@ import java.util.concurrent.Executor;
|
|||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.ConcurrentPlayerMap;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
|
@ -33,6 +31,8 @@ import com.comphenix.protocol.events.PacketEvent;
|
|||
import com.comphenix.protocol.injector.SortedPacketListenerList;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Contains every sending queue for every player.
|
||||
*
|
||||
|
|
|
@ -17,13 +17,12 @@
|
|||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Queue;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,7 +8,6 @@ import java.util.PriorityQueue;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Ticker;
|
||||
|
@ -49,19 +48,14 @@ public class ExpireHashMap<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
private Map<K, ExpireEntry> keyLookup = new HashMap<K, ExpireEntry>();
|
||||
private PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<ExpireEntry>();
|
||||
private final Map<K, ExpireEntry> keyLookup = new HashMap<>();
|
||||
private final PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<>();
|
||||
|
||||
// View of keyLookup with direct values
|
||||
private Map<K, V> valueView = Maps.transformValues(keyLookup, new Function<ExpireEntry, V>() {
|
||||
@Override
|
||||
public V apply(ExpireEntry entry) {
|
||||
return entry.expireValue;
|
||||
}
|
||||
});
|
||||
private final Map<K, V> valueView = Maps.transformValues(keyLookup, entry -> entry.expireValue);
|
||||
|
||||
// Supplied by the constructor
|
||||
private Ticker ticker;
|
||||
private final Ticker ticker;
|
||||
|
||||
/**
|
||||
* Construct a new hash map where each entry may expire at a given time.
|
||||
|
|
|
@ -2,11 +2,11 @@ package com.comphenix.protocol.error;
|
|||
|
||||
import java.io.PrintStream;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
import com.comphenix.protocol.reflect.PrettyPrinter;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents a basic error reporter that prints error reports to the standard error stream.
|
||||
* <p>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package com.comphenix.protocol.error;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Construct an error reporter that delegates to another error reporter.
|
||||
* @author Kristian
|
||||
|
|
|
@ -30,13 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.comphenix.protocol.ProtocolConfig;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang.builder.ToStringStyle;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLogger;
|
||||
import com.comphenix.protocol.collections.ExpireHashMap;
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
|
@ -45,6 +39,11 @@ import com.comphenix.protocol.reflect.PrettyPrinter;
|
|||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.Primitives;
|
||||
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang.builder.ToStringStyle;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Internal class used to handle exceptions.
|
||||
*
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents an object that can forward an error {@link Report} to the display and permanent storage.
|
||||
*
|
||||
|
|
|
@ -4,11 +4,11 @@ import java.io.File;
|
|||
import java.net.URLDecoder;
|
||||
import java.security.CodeSource;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
public final class PluginContext {
|
||||
// Determine plugin folder
|
||||
private static File pluginFolder;
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package com.comphenix.protocol.error;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a error or warning report.
|
||||
*
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package com.comphenix.protocol.error;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents an error reporter that rethrows every exception instead.
|
||||
* @author Kristian
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.comphenix.protocol.events;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.lang.reflect.Array;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
@ -12,7 +17,7 @@ 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.Preconditions;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
|
@ -25,10 +30,6 @@ import org.bukkit.inventory.MerchantRecipe;
|
|||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
|
||||
public abstract class AbstractStructure {
|
||||
protected transient Object handle;
|
||||
protected transient StructureModifier<Object> structureModifier;
|
||||
|
@ -296,17 +297,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 +368,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>
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.wrappers.Converters;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class InternalStructure extends AbstractStructure {
|
||||
|
||||
protected InternalStructure(Object handle, StructureModifier<Object> structureModifier) {
|
||||
|
|
|
@ -17,16 +17,12 @@
|
|||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Determines which packets will be observed by a listener, and with what priority.
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package com.comphenix.protocol.events;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.*;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,13 +17,15 @@
|
|||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,32 +17,6 @@
|
|||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.cloning.AggregateCloner;
|
||||
import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters;
|
||||
import com.comphenix.protocol.reflect.cloning.BukkitCloner;
|
||||
import com.comphenix.protocol.reflect.cloning.Cloner;
|
||||
import com.comphenix.protocol.reflect.cloning.CollectionCloner;
|
||||
import com.comphenix.protocol.reflect.cloning.FieldCloner;
|
||||
import com.comphenix.protocol.reflect.cloning.GuavaOptionalCloner;
|
||||
import com.comphenix.protocol.reflect.cloning.ImmutableDetector;
|
||||
import com.comphenix.protocol.reflect.cloning.JavaOptionalCloner;
|
||||
import com.comphenix.protocol.reflect.cloning.SerializableCloner;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.reflect.instances.MinecraftGenerator;
|
||||
import com.comphenix.protocol.utility.MinecraftMethods;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.wrappers.Converters;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Sets;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
|
@ -56,6 +30,25 @@ import java.util.Set;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.cloning.*;
|
||||
import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.reflect.instances.MinecraftGenerator;
|
||||
import com.comphenix.protocol.utility.MinecraftMethods;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.wrappers.Converters;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||
|
||||
/**
|
||||
* Represents a Minecraft packet indirectly.
|
||||
*
|
||||
|
@ -280,10 +273,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -307,7 +296,7 @@ public class PacketContainer extends AbstractStructure implements Serializable {
|
|||
handle = type.getPacketClass()
|
||||
.getConstructor(MinecraftReflection.getPacketDataSerializerClass())
|
||||
.newInstance(serializer);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException ex) {
|
||||
// they might have a static method to create them instead
|
||||
Method method = FuzzyReflection.fromClass(type.getPacketClass(), true)
|
||||
.getMethod(FuzzyMethodContract
|
||||
|
@ -318,9 +307,11 @@ public class PacketContainer extends AbstractStructure implements Serializable {
|
|||
.build());
|
||||
try {
|
||||
handle = method.invoke(null, serializer);
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
throw new RuntimeException("Failed to construct packet for " + type, ex);
|
||||
} catch (ReflectiveOperationException exception) {
|
||||
throw new RuntimeException("Failed to construct packet for " + type, exception);
|
||||
}
|
||||
} catch (InvocationTargetException ex) {
|
||||
throw new RuntimeException("Unable to clone packet " + type + " using constructor", ex.getCause());
|
||||
}
|
||||
} else {
|
||||
handle = StructureCache.newPacket(type);
|
||||
|
@ -330,10 +321,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,12 @@
|
|||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.EventObject;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
|
@ -29,11 +35,7 @@ import com.google.common.base.Preconditions;
|
|||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.EventObject;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
|
|
|
@ -16,14 +16,15 @@
|
|||
*/
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import org.apache.commons.lang.Validate;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
|
||||
/**
|
||||
* Stores and retrieves metadata for applicable packet objects.
|
||||
* @author dmulloy2
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package com.comphenix.protocol.events;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents an adapter version of a post listener.
|
||||
* @author Kristian
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package com.comphenix.protocol.events;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketStream;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Represents a packet that is scheduled for transmission at a later stage.
|
||||
* @author Kristian
|
||||
|
|
|
@ -28,6 +28,13 @@ import java.util.Map;
|
|||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.comphenix.protocol.utility.ByteBuddyFactory;
|
||||
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.profile.PlayerProfile;
|
||||
|
||||
import net.bytebuddy.description.ByteCodeElement;
|
||||
import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
||||
|
@ -42,13 +49,6 @@ import net.bytebuddy.implementation.bind.annotation.RuntimeType;
|
|||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.profile.PlayerProfile;
|
||||
|
||||
import com.comphenix.protocol.utility.ByteBuddyFactory;
|
||||
|
||||
/**
|
||||
* Represents a player object that can be serialized by Java.
|
||||
*
|
||||
|
|
|
@ -22,11 +22,11 @@ import com.comphenix.protocol.error.ErrorReporter;
|
|||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.primitives.Primitives;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
@ -224,17 +224,14 @@ public class BukkitUnwrapper implements Unwrapper {
|
|||
if (Player.class.isAssignableFrom(type)) {
|
||||
final Method getHandle = MinecraftReflection.getCraftPlayerClass().getMethod("getHandle");
|
||||
|
||||
Unwrapper unwrapper = new Unwrapper() {
|
||||
@Override
|
||||
public Object unwrapItem(Object wrapped) {
|
||||
Unwrapper unwrapper = wrapped -> {
|
||||
try {
|
||||
return getHandle.invoke(((Player) wrapped).getPlayer());
|
||||
} catch (Throwable ex) {
|
||||
try {
|
||||
return getHandle.invoke(((Player) wrapped).getPlayer());
|
||||
} catch (Throwable ex) {
|
||||
try {
|
||||
return getHandle.invoke(Bukkit.getPlayer(((Player) wrapped).getUniqueId()));
|
||||
} catch (ReflectiveOperationException ex1) {
|
||||
throw new RuntimeException("Failed to unwrap proxy " + wrapped, ex);
|
||||
}
|
||||
return getHandle.invoke(Bukkit.getPlayer(((Player) wrapped).getUniqueId()));
|
||||
} catch (ReflectiveOperationException ex1) {
|
||||
throw new RuntimeException("Failed to unwrap proxy " + wrapped, ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -255,21 +252,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 +280,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;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.comphenix.protocol.utility.MinecraftFields;
|
|||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -56,6 +57,7 @@ class EntityUtilities {
|
|||
private FieldAccessor trackedPlayersField;
|
||||
private FieldAccessor trackedEntitiesField;
|
||||
|
||||
private MethodAccessor getEntity;
|
||||
private MethodAccessor getChunkProvider;
|
||||
|
||||
private EntityUtilities() {
|
||||
|
@ -83,8 +85,22 @@ class EntityUtilities {
|
|||
.invoke(trackerEntry, nmsPlayers);
|
||||
}
|
||||
|
||||
public Entity getEntity(World world, int id) {
|
||||
Object level = BukkitUnwrapper.getInstance().unwrapItem(world);
|
||||
if (getEntity == null) {
|
||||
Method entityGetter = FuzzyReflection.fromObject(level).getMethodByReturnTypeAndParameters(
|
||||
"getEntity",
|
||||
MinecraftReflection.getEntityClass(),
|
||||
int.class);
|
||||
getEntity = Accessors.getMethodAccessor(entityGetter);
|
||||
}
|
||||
|
||||
Object entity = getEntity.invoke(level, id);
|
||||
return (Entity) MinecraftReflection.getBukkitEntity(entity);
|
||||
}
|
||||
|
||||
private MethodAccessor findScanPlayers(Class<?> trackerClass) {
|
||||
MethodAccessor candidate = Accessors.getMethodAcccessorOrNull(trackerClass, "scanPlayers");
|
||||
MethodAccessor candidate = Accessors.getMethodAccessorOrNull(trackerClass, "scanPlayers");
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
}
|
||||
|
|
|
@ -401,12 +401,7 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
|||
|
||||
@Override
|
||||
public Entity getEntityFromID(World container, int id) {
|
||||
for (Entity entity : container.getEntities()) {
|
||||
if (entity.getEntityId() == id) {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return EntityUtilities.getInstance().getEntity(container, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -16,6 +16,7 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.logging.Level;
|
||||
|
@ -150,14 +151,9 @@ public class Metrics {
|
|||
}
|
||||
// Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler
|
||||
// Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;)
|
||||
Bukkit.getScheduler().runTask(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
submitData();
|
||||
}
|
||||
});
|
||||
Bukkit.getScheduler().runTask(plugin, Metrics.this::submitData);
|
||||
}
|
||||
}, 1000*60*5, 1000*60*30);
|
||||
}, 1000L * 60 * 5, 1000L * 60 * 30);
|
||||
// Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start
|
||||
// WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted!
|
||||
// WARNING: Just don't do it!
|
||||
|
@ -260,17 +256,14 @@ public class Metrics {
|
|||
data.put("plugins", pluginData);
|
||||
|
||||
// Create a new thread for the connection to the bStats server
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// Send the data
|
||||
sendData(data);
|
||||
} catch (Exception e) {
|
||||
// Something went wrong! :(
|
||||
if (logFailedRequests) {
|
||||
plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e);
|
||||
}
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// Send the data
|
||||
sendData(data);
|
||||
} catch (Exception e) {
|
||||
// Something went wrong! :(
|
||||
if (logFailedRequests) {
|
||||
plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
@ -326,7 +319,7 @@ public class Metrics {
|
|||
}
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
|
||||
gzip.write(str.getBytes("UTF-8"));
|
||||
gzip.write(str.getBytes(StandardCharsets.UTF_8));
|
||||
gzip.close();
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,174 +25,167 @@ import java.util.Map;
|
|||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
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.
|
||||
*/
|
||||
public final static int RECURSE_DEPTH = 3;
|
||||
public static final int RECURSE_DEPTH = 3;
|
||||
|
||||
/**
|
||||
* 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 +197,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 +213,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 +266,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 +278,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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,43 +2,41 @@
|
|||
* 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.cloning;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
|
||||
import com.comphenix.protocol.reflect.instances.InstanceProvider;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implements a cloning procedure by trying multiple methods in turn until one is successful.
|
||||
*
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class AggregateCloner implements Cloner {
|
||||
/**
|
||||
* Supplies the cloner factories with necessary parameters.
|
||||
*
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class BuilderParameters {
|
||||
|
@ -48,11 +46,11 @@ public class AggregateCloner implements Cloner {
|
|||
|
||||
// Used to construct the different types
|
||||
private InstanceProvider typeConstructor;
|
||||
|
||||
|
||||
private BuilderParameters() {
|
||||
// Only allow inner classes to construct it.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the instance provider last set in the builder.
|
||||
* @return Current instance provider.
|
||||
|
@ -69,23 +67,23 @@ public class AggregateCloner implements Cloner {
|
|||
return aggregateCloner;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Represents a builder for aggregate (combined) cloners.
|
||||
*
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Builder {
|
||||
private final List<Function<BuilderParameters, Cloner>> factories = new ArrayList<>();
|
||||
private final BuilderParameters parameters;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new aggregate builder.
|
||||
*/
|
||||
public Builder() {
|
||||
this.parameters = new BuilderParameters();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the instance provider supplied to all cloners in this builder.
|
||||
* @param provider - new instance provider.
|
||||
|
@ -103,23 +101,16 @@ public class AggregateCloner implements Cloner {
|
|||
*/
|
||||
public Builder andThen(final Class<? extends Cloner> type) {
|
||||
// Use reflection to generate a factory on the fly
|
||||
return andThen(new Function<BuilderParameters, Cloner>() {
|
||||
@Override
|
||||
public Cloner apply(@Nullable BuilderParameters param) {
|
||||
Object result = param.typeConstructor.create(type);
|
||||
|
||||
if (result == null) {
|
||||
throw new IllegalStateException("Constructed NULL instead of " + type);
|
||||
}
|
||||
|
||||
if (type.isAssignableFrom(result.getClass()))
|
||||
return (Cloner) result;
|
||||
else
|
||||
throw new IllegalStateException("Constructed " + result.getClass() + " instead of " + type);
|
||||
}
|
||||
return andThen(param -> {
|
||||
final Object result = param.typeConstructor.create(type);
|
||||
|
||||
if (result == null) throw new IllegalStateException("Constructed NULL instead of " + type);
|
||||
|
||||
if (type.isAssignableFrom(result.getClass())) return (Cloner) result;
|
||||
else throw new IllegalStateException("Constructed " + result.getClass() + " instead of " + type);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add the next cloner that will be considered in turn.
|
||||
* @param factory - factory constructing the next cloner.
|
||||
|
@ -129,45 +120,41 @@ public class AggregateCloner implements Cloner {
|
|||
factories.add(factory);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build a new aggregate cloner using the supplied values.
|
||||
* @return A new aggregate cloner.
|
||||
*/
|
||||
public AggregateCloner build() {
|
||||
AggregateCloner newCloner = new AggregateCloner();
|
||||
|
||||
|
||||
// The parameters we will pass to our cloners
|
||||
Cloner paramCloner = new NullableCloner(newCloner);
|
||||
InstanceProvider paramProvider = parameters.instanceProvider;
|
||||
|
||||
|
||||
// Initialize parameters
|
||||
parameters.aggregateCloner = paramCloner;
|
||||
parameters.typeConstructor = DefaultInstances.fromArray(
|
||||
ExistingGenerator.fromObjectArray(new Object[] { paramCloner, paramProvider })
|
||||
);
|
||||
|
||||
|
||||
// Build every cloner in the correct order
|
||||
final List<Cloner> cloners = new ArrayList<>();
|
||||
|
||||
|
||||
for (int i = 0; i < factories.size(); i++) {
|
||||
Cloner cloner = factories.get(i).apply(parameters);
|
||||
|
||||
|
||||
// See if we were successful
|
||||
if (cloner != null)
|
||||
cloners.add(cloner);
|
||||
else
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Cannot create cloner from %s (%s)", factories.get(i), i)
|
||||
);
|
||||
if (cloner != null) cloners.add(cloner);
|
||||
else throw new IllegalArgumentException(String.format("Cannot create cloner from %s (%s)", factories.get(i), i));
|
||||
}
|
||||
|
||||
|
||||
// We're done
|
||||
newCloner.setCloners(cloners);
|
||||
return newCloner;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Represents a default aggregate cloner.
|
||||
*/
|
||||
|
@ -178,13 +165,13 @@ public class AggregateCloner implements Cloner {
|
|||
andThen(CollectionCloner.class).
|
||||
andThen(FieldCloner.class).
|
||||
build();
|
||||
|
||||
|
||||
// List of clone methods
|
||||
private List<Cloner> cloners;
|
||||
|
||||
private WeakReference<Object> lastObject;
|
||||
private int lastResult;
|
||||
|
||||
|
||||
/**
|
||||
* Begins constructing a new aggregate cloner.
|
||||
* @return A builder for a new aggregate cloner.
|
||||
|
@ -192,14 +179,14 @@ public class AggregateCloner implements Cloner {
|
|||
public static Builder newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new, empty aggregate cloner.
|
||||
*/
|
||||
private AggregateCloner() {
|
||||
// Only used by our builder above.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a view of the current list of cloners.
|
||||
* @return Current cloners.
|
||||
|
@ -207,23 +194,23 @@ public class AggregateCloner implements Cloner {
|
|||
public List<Cloner> getCloners() {
|
||||
return Collections.unmodifiableList(cloners);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the cloners that will be used.
|
||||
* @param cloners - the cloners that will be used.
|
||||
*/
|
||||
private void setCloners(Iterable<? extends Cloner> cloners) {
|
||||
this.cloners = Lists.newArrayList(cloners);
|
||||
private void setCloners(Collection<? extends Cloner> cloners) {
|
||||
this.cloners = new ArrayList<>(cloners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canClone(Object source) {
|
||||
// Optimize a bit
|
||||
lastResult = getFirstCloner(source);
|
||||
lastObject = new WeakReference<Object>(source);
|
||||
lastObject = new WeakReference<>(source);
|
||||
return lastResult >= 0 && lastResult < cloners.size();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the index of the first cloner capable of cloning the given object.
|
||||
* <p>
|
||||
|
@ -236,7 +223,7 @@ public class AggregateCloner implements Cloner {
|
|||
if (cloners.get(i).canClone(source))
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
return cloners.size();
|
||||
}
|
||||
|
||||
|
@ -245,7 +232,7 @@ public class AggregateCloner implements Cloner {
|
|||
if (source == null)
|
||||
throw new IllegalAccessError("source cannot be NULL.");
|
||||
int index = 0;
|
||||
|
||||
|
||||
// Are we dealing with the same object?
|
||||
if (lastObject != null && lastObject.get() == source) {
|
||||
index = lastResult;
|
||||
|
|
|
@ -30,13 +30,12 @@ import com.comphenix.protocol.reflect.StructureModifier;
|
|||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.*;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
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 +74,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);
|
||||
|
@ -91,8 +89,7 @@ public class BukkitCloner implements Cloner {
|
|||
} catch (Throwable ignored) { }
|
||||
|
||||
try {
|
||||
fromManual(AdventureComponentConverter::getComponentClass, source ->
|
||||
AdventureComponentConverter.clone(source));
|
||||
fromManual(AdventureComponentConverter::getComponentClass, AdventureComponentConverter::clone);
|
||||
} catch (Throwable ignored) { }
|
||||
}
|
||||
|
||||
|
@ -133,7 +130,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
|
||||
}
|
||||
}
|
|
@ -4,164 +4,32 @@ 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 +37,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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,18 +5,19 @@ 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.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 net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
||||
import net.bytebuddy.implementation.MethodDelegation;
|
||||
import net.bytebuddy.implementation.bind.annotation.Origin;
|
||||
|
@ -26,158 +27,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 +195,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
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue