Package com.comphenix.protocol.wrappers.nbt

Source Code of com.comphenix.protocol.wrappers.nbt.TileEntityAccessor

package com.comphenix.protocol.wrappers.nbt;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentMap;

import net.minecraft.server.v1_7_R3.NBTTagCompound;
import net.minecraft.server.v1_7_R3.TileEntityChest;
import net.sf.cglib.asm.ClassReader;
import net.sf.cglib.asm.MethodVisitor;
import net.sf.cglib.asm.Opcodes;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import org.bukkit.block.BlockState;

import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.compiler.EmptyClassVisitor;
import com.comphenix.protocol.reflect.compiler.EmptyMethodVisitor;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps;

/**
* Manipulate tile entities.
* @author Kristian
*/
class TileEntityAccessor<T extends BlockState> {
  /**
   * Token indicating that the given block state doesn't contany any tile entities.
   */
  private static final TileEntityAccessor<BlockState> EMPTY_ACCESSOR = new TileEntityAccessor<BlockState>();
 
  /**
   * Cached field accessors - {@link #EMPTY_ACCESSOR} represents no valid tile entity.
   */
  private static final ConcurrentMap<Class<?>, TileEntityAccessor<?>> cachedAccessors = Maps.newConcurrentMap();
 
  private FieldAccessor tileEntityField;
  private MethodAccessor readCompound;
  private MethodAccessor writeCompound;
 
  // For CGLib detection
  private boolean writeDetected;
  private boolean readDetected;
 
  private TileEntityAccessor() {
    // Do nothing
  }
 
  /**
   * Construct a new tile entity accessor.
   * @param tileEntityField - the tile entity field.
   * @param tileEntity - the tile entity.
   * @param tile - the block state.
   * @throws IOException Cannot read tile entity.
   */
  private TileEntityAccessor(FieldAccessor tileEntityField, T state) {
    if (tileEntityField != null) {
      this.tileEntityField = tileEntityField;
      Class<?> type = tileEntityField.getField().getType();
     
      // Possible read/write methods
      try {
        findMethodsUsingASM(type);
      } catch (IOException ex1) {
        try {
          // Much slower though
          findMethodUsingCGLib(state);
        } catch (Exception ex2) {
          throw new RuntimeException("Cannot find read/write methods in " + type, ex2);
        }
      }
     
      // Ensure we found them
      if (readCompound == null)
        throw new RuntimeException("Unable to find read method in " + type);
      if (writeCompound == null)
        throw new RuntimeException("Unable to find write method in " + type);
    }
  }

  /**
   * Find the read/write methods in TileEntity.
   * @param tileEntityClass - the tile entity class.
   * @param nbtCompoundClass - the compound clas.
   * @throws IOException If we cannot find these methods.
   */
  private void findMethodsUsingASM(final Class<?> tileEntityClass) throws IOException {
    final Class<?> nbtCompoundClass = MinecraftReflection.getNBTCompoundClass();
    final ClassReader reader = new ClassReader(tileEntityClass.getCanonicalName());
   
    final String tagCompoundName = getJarName(MinecraftReflection.getNBTCompoundClass());
    final String expectedDesc = "(L" + tagCompoundName + ";)V";
   
    reader.accept(new EmptyClassVisitor() {
      @Override
      public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        final String methodName = name;
       
        // Detect read/write calls to NBTTagCompound
        if (expectedDesc.equals(desc)) {
          return new EmptyMethodVisitor() {
            private int readMethods;
            private int writeMethods;
           
            public void visitMethodInsn(int opcode, String owner, String name, String desc) {
              // This must be a virtual call on NBTTagCompound that accepts a String
              if (opcode == Opcodes.INVOKEVIRTUAL &&
                tagCompoundName.equals(owner) &&
                desc.startsWith("(Ljava/lang/String")) {
               
                // Is this a write call?
                if (desc.endsWith(")V")) {
                  writeMethods++;
                } else {
                  readMethods++;
                }
              }              
            }
           
            @Override
            public void visitEnd() {
              if (readMethods > writeMethods) {
                readCompound = Accessors.getMethodAccessor(tileEntityClass, methodName, nbtCompoundClass);
              } else if (writeMethods > readMethods) {
                writeCompound = Accessors.getMethodAccessor(tileEntityClass, methodName, nbtCompoundClass);
              }
              super.visitEnd();
            }
          };
        }
        return null;
      }
    }, 0);
  }
 
  /**
   * Find the read/write methods in TileEntity.
   * @param blockState - the block state.
   * @throws IOException If we cannot find these methods.
   */
  private void findMethodUsingCGLib(T blockState) throws IOException {
    final Class<?> nbtCompoundClass = MinecraftReflection.getNBTCompoundClass();
   
    // This is a much slower method, but it is necessary in MCPC
    Enhancer enhancer = EnhancerFactory.getInstance().createEnhancer();
    enhancer.setSuperclass(nbtCompoundClass);
    enhancer.setCallback(new MethodInterceptor() {
      @Override
      public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        if (method.getReturnType().equals(Void.TYPE)) {
          // Write method
          writeDetected = true;
        } else {
          // Read method
          readDetected = true;
        }
        throw new RuntimeException("Stop execution.");
      }
    });
    Object compound = enhancer.create();
    Object tileEntity = tileEntityField.get(blockState);
   
    // Look in every read/write like method
    for (Method method : FuzzyReflection.fromObject(tileEntity, true).
        getMethodListByParameters(Void.TYPE, new Class<?>[] { nbtCompoundClass })) {
     
      try {
        readDetected = false;
        writeDetected = false;
        method.invoke(tileEntity, compound);
      } catch (Exception e) {
        // Okay - see if we detected a write or read
        if (readDetected)
          readCompound = Accessors.getMethodAccessor(method, true);
        if (writeDetected)
          writeCompound = Accessors.getMethodAccessor(method, true);
      }
    }
  }
 
  /**
   * Retrieve the JAR name (slash instead of dots) of the given clas.
   * @param clazz - the class.
   * @return The JAR name.
   */
  private static String getJarName(Class<?> clazz) {
    return clazz.getCanonicalName().replace('.', '/');
  }
 
  /**
   * Read the NBT compound that represents a given tile entity.
   * @param state - tile entity represented by a block state.
   * @return The compound.
   */
  public NbtCompound readBlockState(T state) {
    NbtCompound output = NbtFactory.ofCompound("");
    Object tileEntity = tileEntityField.get(state);
   
    // Write the block state to the output compound
    writeCompound.invoke(tileEntity, NbtFactory.fromBase(output).getHandle());
    return output;
  }
 
  /**
   * Write the NBT compound as a tile entity.
   * @param state - target block state.
   * @param compound - the compound.
   */
  public void writeBlockState(T state, NbtCompound compound) {
    Object tileEntity = tileEntityField.get(state);
   
    // Ensure the block state is set to the compound
    readCompound.invoke(tileEntity, NbtFactory.fromBase(compound).getHandle());
  }
 
  /**
   * Retrieve an accessor for the tile entity at a specific location.
   * @param state - the block state.
   * @return The accessor, or NULL if this block state doesn't contain any tile entities.
   */
  @SuppressWarnings("unchecked")
  public static <T extends BlockState> TileEntityAccessor<T> getAccessor(T state) {
    Class<?> craftBlockState = state.getClass();
    TileEntityAccessor<?> accessor = cachedAccessors.get(craftBlockState);
   
    // Attempt to construct the accessor
    if (accessor == null ) {
      TileEntityAccessor<?> created = null;
      FieldAccessor field = null;
     
      try {
        field = Accessors.getFieldAccessor(craftBlockState, MinecraftReflection.getTileEntityClass(), true);
      } catch (Exception e) {
        created = EMPTY_ACCESSOR;
      }
      if (field != null) {
        created = new TileEntityAccessor<T>(field, state);
      }
      accessor = cachedAccessors.putIfAbsent(craftBlockState, created);
   
      // We won the race
      if (accessor == null) {
        accessor = created;
      }
    }
    return (TileEntityAccessor<T>) (accessor != EMPTY_ACCESSOR ? accessor : null);
  }
}
TOP

Related Classes of com.comphenix.protocol.wrappers.nbt.TileEntityAccessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.