Complicated feature - auto-compilation of structure modifier.
Using ASM we can automatically generate a faster structure modifier that doesn't use reflection to read or write public fields. Note that because the compilation itself is a bit slow (10 ms++), we have to create a background compilation thread. Future work: * Disable the thread if it's idle after 60 seconds. * Don't recreate the thread when the server reloads.
Dieser Commit ist enthalten in:
Ursprung
90970d1b9b
Commit
240df9dc7a
@ -25,6 +25,7 @@ import org.bukkit.Server;
|
|||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.compiler.BackgroundCompiler;
|
||||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||||
import com.comphenix.protocol.metrics.Statistics;
|
import com.comphenix.protocol.metrics.Statistics;
|
||||||
|
|
||||||
@ -39,6 +40,9 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
// Metrics and statistisc
|
// Metrics and statistisc
|
||||||
private Statistics statistisc;
|
private Statistics statistisc;
|
||||||
|
|
||||||
|
// Structure compiler
|
||||||
|
private BackgroundCompiler backgroundCompiler;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoad() {
|
public void onLoad() {
|
||||||
logger = getLoggerSafely();
|
logger = getLoggerSafely();
|
||||||
@ -50,6 +54,12 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
Server server = getServer();
|
Server server = getServer();
|
||||||
PluginManager manager = server.getPluginManager();
|
PluginManager manager = server.getPluginManager();
|
||||||
|
|
||||||
|
// Initialize background compiler
|
||||||
|
if (backgroundCompiler == null) {
|
||||||
|
backgroundCompiler = new BackgroundCompiler(getClassLoader());
|
||||||
|
BackgroundCompiler.setInstance(backgroundCompiler);
|
||||||
|
}
|
||||||
|
|
||||||
// Notify server managers of incompatible plugins
|
// Notify server managers of incompatible plugins
|
||||||
checkForIncompatibility(manager);
|
checkForIncompatibility(manager);
|
||||||
|
|
||||||
@ -83,6 +93,13 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
|
// Disable compiler
|
||||||
|
if (backgroundCompiler != null) {
|
||||||
|
backgroundCompiler.shutdownAll();
|
||||||
|
backgroundCompiler = null;
|
||||||
|
BackgroundCompiler.setInstance(null);
|
||||||
|
}
|
||||||
|
|
||||||
protocolManager.close();
|
protocolManager.close();
|
||||||
protocolManager = null;
|
protocolManager = null;
|
||||||
statistisc = null;
|
statistisc = null;
|
||||||
|
162
ProtocolLib/src/com/comphenix/protocol/compiler/BackgroundCompiler.java
Normale Datei
162
ProtocolLib/src/com/comphenix/protocol/compiler/BackgroundCompiler.java
Normale Datei
@ -0,0 +1,162 @@
|
|||||||
|
package com.comphenix.protocol.compiler;
|
||||||
|
|
||||||
|
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.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles structure modifiers on a background thread.
|
||||||
|
* <p>
|
||||||
|
* This is necessary as we cannot block the main thread.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class BackgroundCompiler {
|
||||||
|
|
||||||
|
// How long to wait for a shutdown
|
||||||
|
public static final int SHUTDOWN_DELAY_MS = 2000;
|
||||||
|
|
||||||
|
// The single background compiler we're using
|
||||||
|
private static BackgroundCompiler backgroundCompiler;
|
||||||
|
|
||||||
|
private StructureCompiler compiler;
|
||||||
|
private boolean enabled;
|
||||||
|
private boolean shuttingDown;
|
||||||
|
|
||||||
|
private ExecutorService executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @param loader - class loader from Bukkit.
|
||||||
|
*/
|
||||||
|
public BackgroundCompiler(ClassLoader loader) {
|
||||||
|
this(loader, Executors.newSingleThreadExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a background compiler utilizing the given thread pool.
|
||||||
|
* @param loader - class loader from Bukkit.
|
||||||
|
* @param executor - thread pool we'll use.
|
||||||
|
*/
|
||||||
|
public BackgroundCompiler(ClassLoader loader, ExecutorService executor) {
|
||||||
|
if (loader == null)
|
||||||
|
throw new IllegalArgumentException("loader cannot be NULL");
|
||||||
|
if (executor == null)
|
||||||
|
throw new IllegalArgumentException("executor cannot be NULL");
|
||||||
|
|
||||||
|
this.compiler = new StructureCompiler(loader);
|
||||||
|
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) {
|
||||||
|
|
||||||
|
// Only schedule if we're enabled
|
||||||
|
if (enabled && !shuttingDown) {
|
||||||
|
|
||||||
|
// Don't try to schedule anything
|
||||||
|
if (executor == null || executor.isShutdown())
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
executor.submit(new Callable<Object>() {
|
||||||
|
@Override
|
||||||
|
public Object call() throws Exception {
|
||||||
|
|
||||||
|
StructureModifier<?> modifier = cache.get(key);
|
||||||
|
|
||||||
|
// Update the cache!
|
||||||
|
modifier = compiler.compile(modifier);
|
||||||
|
cache.put(key, modifier);
|
||||||
|
|
||||||
|
// We'll also return the new structure modifier
|
||||||
|
return modifier;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} 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.
|
||||||
|
Logger.getLogger("Minecraft").log(Level.WARNING, "Unable to schedule compilation task.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
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 current structure compiler.
|
||||||
|
* @return Current structure compiler.
|
||||||
|
*/
|
||||||
|
public StructureCompiler getCompiler() {
|
||||||
|
return compiler;
|
||||||
|
}
|
||||||
|
}
|
275
ProtocolLib/src/com/comphenix/protocol/compiler/BoxingHelper.java
Normale Datei
275
ProtocolLib/src/com/comphenix/protocol/compiler/BoxingHelper.java
Normale Datei
@ -0,0 +1,275 @@
|
|||||||
|
package com.comphenix.protocol.compiler;
|
||||||
|
|
||||||
|
import net.sf.cglib.asm.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.comphenix.protocol.compiler;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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
|
||||||
|
* @param <TField> Field type.
|
||||||
|
*/
|
||||||
|
public class CompiledStructureModifier<TField> extends StructureModifier<TField> {
|
||||||
|
// Used to compile instances of structure modifiers
|
||||||
|
protected StructureCompiler compiler;
|
||||||
|
|
||||||
|
// Speed up the default writer
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public StructureModifier<TField> 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();
|
||||||
|
|
||||||
|
write(index, (TField) generator.getDefault(field.getType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StructureModifier<TField> withTarget(Object target) {
|
||||||
|
if (compiler != null)
|
||||||
|
return compiler.compile(super.withTarget(target));
|
||||||
|
else
|
||||||
|
return super.withTarget(target);
|
||||||
|
}
|
||||||
|
}
|
220
ProtocolLib/src/com/comphenix/protocol/compiler/MethodDescriptor.java
Normale Datei
220
ProtocolLib/src/com/comphenix/protocol/compiler/MethodDescriptor.java
Normale Datei
@ -0,0 +1,220 @@
|
|||||||
|
package com.comphenix.protocol.compiler;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import net.sf.cglib.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 Method}.
|
||||||
|
*
|
||||||
|
* @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 Method}.
|
||||||
|
*
|
||||||
|
* @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 Method} 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 Method} 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 Method} 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 Method} 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();
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
int index = 0;
|
||||||
|
while ((index = type.indexOf("[]", index) + 1) > 0) {
|
||||||
|
sb.append('[');
|
||||||
|
}
|
||||||
|
|
||||||
|
String t = type.substring(0, type.length() - sb.length() * 2);
|
||||||
|
String desc = (String) 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();
|
||||||
|
}
|
||||||
|
}
|
432
ProtocolLib/src/com/comphenix/protocol/compiler/StructureCompiler.java
Normale Datei
432
ProtocolLib/src/com/comphenix/protocol/compiler/StructureCompiler.java
Normale Datei
@ -0,0 +1,432 @@
|
|||||||
|
package com.comphenix.protocol.compiler;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.PrimitiveUtils;
|
||||||
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
|
||||||
|
import net.sf.cglib.asm.*;
|
||||||
|
|
||||||
|
// This class will automatically generate the following type of structure modifier:
|
||||||
|
//
|
||||||
|
// public class CompiledStructure$Packet20NamedEntitySpawnObject<TField> extends CompiledStructureModifier<TField> {
|
||||||
|
//
|
||||||
|
// private Packet20NamedEntitySpawn typedTarget;
|
||||||
|
//
|
||||||
|
// public CompiledStructure$Packet20NamedEntitySpawnObject(StructureModifier<TField> other, StructureCompiler compiler) {
|
||||||
|
// initialize(other);
|
||||||
|
// this.typedTarget = (Packet20NamedEntitySpawn) other.getTarget();
|
||||||
|
// this.compiler = compiler;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @SuppressWarnings("unchecked")
|
||||||
|
// @Override
|
||||||
|
// public TField read(int fieldIndex) throws FieldAccessException {
|
||||||
|
//
|
||||||
|
// Packet20NamedEntitySpawn target = typedTarget;
|
||||||
|
//
|
||||||
|
// switch (fieldIndex) {
|
||||||
|
// case 0: return (TField) (Object) target.a;
|
||||||
|
// case 1: return (TField) (Object) target.b;
|
||||||
|
// case 2: return (TField) (Object) target.c;
|
||||||
|
// case 3: return (TField) (Object) target.d;
|
||||||
|
// case 4: return (TField) (Object) target.e;
|
||||||
|
// case 5: return (TField) (Object) target.f;
|
||||||
|
// case 6: return (TField) (Object) target.g;
|
||||||
|
// case 7: return (TField) (Object) target.h;
|
||||||
|
// default:
|
||||||
|
// throw new IllegalArgumentException("Invalid index " + fieldIndex);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public StructureModifier<TField> write(int index, Object value) {
|
||||||
|
//
|
||||||
|
// 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: target.e = (Integer) value; break;
|
||||||
|
// case 5: target.f = (Byte) value; break;
|
||||||
|
// case 6: target.g = (Byte) value; break;
|
||||||
|
// case 7: target.h = (Integer) value; break;
|
||||||
|
// default:
|
||||||
|
// throw new IllegalArgumentException("Invalid index " + index);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Chaining
|
||||||
|
// return this;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a StructureModifier compiler.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public final class StructureCompiler {
|
||||||
|
|
||||||
|
// Used to store generated classes of different types
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private class StructureKey {
|
||||||
|
private Class targetType;
|
||||||
|
private Class fieldType;
|
||||||
|
|
||||||
|
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 static Method defineMethod;
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private Map<StructureKey, Class> compiledCache = new HashMap<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/compiler";
|
||||||
|
private static String SUPER_CLASS = "com/comphenix/protocol/reflect/StructureModifier";
|
||||||
|
private static String COMPILED_CLASS = PACKAGE_NAME + "/CompiledStructureModifier";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a structure compiler.
|
||||||
|
* @param loader - main class loader.
|
||||||
|
*/
|
||||||
|
StructureCompiler(ClassLoader loader) {
|
||||||
|
this.loader = loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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.getTargetType(), source.getFieldType());
|
||||||
|
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 (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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <TField> Class<?> generateClass(StructureModifier<TField> source) {
|
||||||
|
|
||||||
|
ClassWriter cw = new ClassWriter(0);
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Class targetType = source.getTargetType();
|
||||||
|
|
||||||
|
String className = "CompiledStructure$" + targetType.getSimpleName() + source.getFieldType().getSimpleName();
|
||||||
|
String targetSignature = Type.getDescriptor(targetType);
|
||||||
|
String targetName = targetType.getName().replace('.', '/');
|
||||||
|
|
||||||
|
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className,
|
||||||
|
"<TField:Ljava/lang/Object;>L" + COMPILED_CLASS + "<TTField;>;",
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Call the define method
|
||||||
|
try {
|
||||||
|
if (defineMethod == null) {
|
||||||
|
defineMethod = ClassLoader.class.getDeclaredMethod("defineClass",
|
||||||
|
new Class<?>[] { String.class, byte[].class, int.class, int.class });
|
||||||
|
|
||||||
|
// Awesome. Now, create and return it.
|
||||||
|
defineMethod.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Class clazz = (Class) defineMethod.invoke(loader, null, data, 0, data.length);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new RuntimeException("Cannot use reflection to dynamically load a class.", e);
|
||||||
|
} catch (NoSuchMethodException 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 occured 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 (int i = 0; i < fields.size(); i++) {
|
||||||
|
if (isPublic(fields.get(i))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPublic(Field field) {
|
||||||
|
return Modifier.isPublic(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 = "(ITTField;)L" + SUPER_CLASS + "<TTField;>;";
|
||||||
|
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, "write", methodDescriptor, methodSignature,
|
||||||
|
new String[] { "com/comphenix/protocol/reflect/FieldAccessException" });
|
||||||
|
BoxingHelper boxingHelper = new BoxingHelper(mv);
|
||||||
|
|
||||||
|
mv.visitCode();
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "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 errorLabel = new Label();
|
||||||
|
Label returnLabel = new Label();
|
||||||
|
|
||||||
|
// Generate labels
|
||||||
|
for (int i = 0; i < fields.size(); i++) {
|
||||||
|
labels[i] = new Label();
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitTableSwitchInsn(0, labels.length - 1, errorLabel, labels);
|
||||||
|
|
||||||
|
for (int i = 0; i < fields.size(); i++) {
|
||||||
|
|
||||||
|
Class<?> outputType = fields.get(i).getType();
|
||||||
|
Class<?> inputType = PrimitiveUtils.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 fields
|
||||||
|
if (isPublic(fields.get(i))) {
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 3);
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||||
|
|
||||||
|
if (!PrimitiveUtils.isPrimitive(outputType))
|
||||||
|
mv.visitTypeInsn(Opcodes.CHECKCAST, inputPath);
|
||||||
|
else
|
||||||
|
boxingHelper.unbox(Type.getType(outputType));
|
||||||
|
|
||||||
|
mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, fields.get(i).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.INVOKESPECIAL, COMPILED_CLASS, "write", "(ILjava/lang/Object;)L" + SUPER_CLASS + ";");
|
||||||
|
mv.visitInsn(Opcodes.POP);
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitJumpInsn(Opcodes.GOTO, returnLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitLabel(errorLabel);
|
||||||
|
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||||
|
mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalArgumentException");
|
||||||
|
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, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V");
|
||||||
|
mv.visitInsn(Opcodes.ATHROW);
|
||||||
|
|
||||||
|
mv.visitLabel(returnLabel);
|
||||||
|
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_PUBLIC + Opcodes.ACC_FINAL, "read", "(I)Ljava/lang/Object;", "(I)TTField;",
|
||||||
|
new String[] { "com/comphenix/protocol/reflect/FieldAccessException" });
|
||||||
|
BoxingHelper boxingHelper = new BoxingHelper(mv);
|
||||||
|
|
||||||
|
mv.visitCode();
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "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 errorLabel = new Label();
|
||||||
|
|
||||||
|
// Generate labels
|
||||||
|
for (int i = 0; i < fields.size(); i++) {
|
||||||
|
labels[i] = new Label();
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitTableSwitchInsn(0, fields.size() - 1, errorLabel, labels);
|
||||||
|
|
||||||
|
for (int i = 0; i < fields.size(); i++) {
|
||||||
|
Class<?> outputType = fields.get(i).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(fields.get(i))) {
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||||
|
mv.visitFieldInsn(Opcodes.GETFIELD, targetName, fields.get(i).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.INVOKESPECIAL, COMPILED_CLASS, "read", "(I)Ljava/lang/Object;");
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitInsn(Opcodes.ARETURN);
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitLabel(errorLabel);
|
||||||
|
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||||
|
mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalArgumentException");
|
||||||
|
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, "java/lang/IllegalArgumentException", "<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 + "<TTField;>;L" + SUPER_CLASS + ";)V", null);
|
||||||
|
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, PACKAGE_NAME + "/" + className, "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, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
|
||||||
|
mv.visitTypeInsn(Opcodes.CHECKCAST, targetName);
|
||||||
|
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||||
|
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;");
|
||||||
|
mv.visitInsn(Opcodes.RETURN);
|
||||||
|
mv.visitMaxs(2, 3);
|
||||||
|
mv.visitEnd();
|
||||||
|
}
|
||||||
|
}
|
@ -21,15 +21,23 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.compiler.BackgroundCompiler;
|
||||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides list-oriented access to the fields of a Minecraft packet.
|
||||||
|
* <p>
|
||||||
|
* Implemented by using reflection. Use a CompiledStructureModifier, if speed is essential.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
* @param <TField> Type of the fields to retrieve.
|
||||||
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public class StructureModifier<TField> {
|
public class StructureModifier<TField> {
|
||||||
|
|
||||||
@ -45,7 +53,7 @@ public class StructureModifier<TField> {
|
|||||||
protected List<Field> data = new ArrayList<Field>();
|
protected List<Field> data = new ArrayList<Field>();
|
||||||
|
|
||||||
// Improved default values
|
// Improved default values
|
||||||
protected Set<Field> defaultFields;
|
protected Map<Field, Integer> defaultFields;
|
||||||
|
|
||||||
// Cache of previous types
|
// Cache of previous types
|
||||||
protected Map<Class, StructureModifier> subtypeCache;
|
protected Map<Class, StructureModifier> subtypeCache;
|
||||||
@ -58,9 +66,9 @@ public class StructureModifier<TField> {
|
|||||||
*/
|
*/
|
||||||
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
|
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
|
||||||
List<Field> fields = getFields(targetType, superclassExclude);
|
List<Field> fields = getFields(targetType, superclassExclude);
|
||||||
Set<Field> defaults = requireDefault ? generateDefaultFields(fields) : new HashSet<Field>();
|
Map<Field, Integer> defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<Field, Integer>();
|
||||||
|
|
||||||
initialize(targetType, Object.class, fields, defaults, null, new HashMap<Class, StructureModifier>());
|
initialize(targetType, Object.class, fields, defaults, null, new ConcurrentHashMap<Class, StructureModifier>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,7 +97,7 @@ public class StructureModifier<TField> {
|
|||||||
* @param subTypeCache - a structure modifier cache.
|
* @param subTypeCache - a structure modifier cache.
|
||||||
*/
|
*/
|
||||||
protected void initialize(Class targetType, Class fieldType,
|
protected void initialize(Class targetType, Class fieldType,
|
||||||
List<Field> data, Set<Field> defaultFields,
|
List<Field> data, Map<Field, Integer> defaultFields,
|
||||||
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
|
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
|
||||||
this.targetType = targetType;
|
this.targetType = targetType;
|
||||||
this.fieldType = fieldType;
|
this.fieldType = fieldType;
|
||||||
@ -213,7 +221,7 @@ public class StructureModifier<TField> {
|
|||||||
DefaultInstances generator = DefaultInstances.DEFAULT;
|
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||||
|
|
||||||
// Write a default instance to every field
|
// Write a default instance to every field
|
||||||
for (Field field : defaultFields) {
|
for (Field field : defaultFields.keySet()) {
|
||||||
try {
|
try {
|
||||||
FieldUtils.writeField(field, target,
|
FieldUtils.writeField(field, target,
|
||||||
generator.getDefault(field.getType()), true);
|
generator.getDefault(field.getType()), true);
|
||||||
@ -239,22 +247,32 @@ public class StructureModifier<TField> {
|
|||||||
// Do we need to update the cache?
|
// Do we need to update the cache?
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
List<Field> filtered = new ArrayList<Field>();
|
List<Field> filtered = new ArrayList<Field>();
|
||||||
Set<Field> defaults = new HashSet<Field>();
|
Map<Field, Integer> defaults = new HashMap<Field, Integer>();
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
for (Field field : data) {
|
for (Field field : data) {
|
||||||
if (fieldType != null && fieldType.isAssignableFrom(field.getType())) {
|
if (fieldType != null && fieldType.isAssignableFrom(field.getType())) {
|
||||||
filtered.add(field);
|
filtered.add(field);
|
||||||
|
|
||||||
if (defaultFields.contains(field))
|
// Don't use the original index
|
||||||
defaults.add(field);
|
if (defaultFields.containsKey(field))
|
||||||
|
defaults.put(field, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep track of the field index
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache structure modifiers
|
// Cache structure modifiers
|
||||||
result = withFieldType(fieldType, filtered, defaults, converter);
|
result = withFieldType(fieldType, filtered, defaults, converter);
|
||||||
|
|
||||||
if (fieldType != null)
|
if (fieldType != null) {
|
||||||
subtypeCache.put(fieldType, result);
|
subtypeCache.put(fieldType, result);
|
||||||
|
|
||||||
|
// Automatically compile the structure modifier
|
||||||
|
if (BackgroundCompiler.getInstance() != null)
|
||||||
|
BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the target too
|
// Add the target too
|
||||||
@ -320,11 +338,11 @@ public class StructureModifier<TField> {
|
|||||||
*/
|
*/
|
||||||
protected <T> StructureModifier<T> withFieldType(
|
protected <T> StructureModifier<T> withFieldType(
|
||||||
Class fieldType, List<Field> filtered,
|
Class fieldType, List<Field> filtered,
|
||||||
Set<Field> defaults, EquivalentConverter<T> converter) {
|
Map<Field, Integer> defaults, EquivalentConverter<T> converter) {
|
||||||
|
|
||||||
StructureModifier<T> result = new StructureModifier<T>();
|
StructureModifier<T> result = new StructureModifier<T>();
|
||||||
result.initialize(targetType, fieldType, filtered, defaults,
|
result.initialize(targetType, fieldType, filtered, defaults,
|
||||||
converter, new HashMap<Class, StructureModifier>());
|
converter, new ConcurrentHashMap<Class, StructureModifier>());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,10 +405,11 @@ public class StructureModifier<TField> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Used to generate plausible default values
|
// Used to generate plausible default values
|
||||||
private static Set<Field> generateDefaultFields(List<Field> fields) {
|
private static Map<Field, Integer> generateDefaultFields(List<Field> fields) {
|
||||||
|
|
||||||
Set<Field> requireDefaults = new HashSet<Field>();
|
Map<Field, Integer> requireDefaults = new HashMap<Field, Integer>();
|
||||||
DefaultInstances generator = DefaultInstances.DEFAULT;
|
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
for (Field field : fields) {
|
for (Field field : fields) {
|
||||||
Class<?> type = field.getType();
|
Class<?> type = field.getType();
|
||||||
@ -400,9 +419,12 @@ public class StructureModifier<TField> {
|
|||||||
// Next, see if we actually can generate a default value
|
// Next, see if we actually can generate a default value
|
||||||
if (generator.getDefault(type) != null) {
|
if (generator.getDefault(type) != null) {
|
||||||
// If so, require it
|
// If so, require it
|
||||||
requireDefaults.add(field);
|
requireDefaults.put(field, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Increment field index
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return requireDefaults;
|
return requireDefaults;
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren