com.comphenix.protocol.events.PacketContainer.java Source code

Java tutorial

Introduction

Here is the source code for com.comphenix.protocol.events.PacketContainer.java

Source

/*
 *  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.events;

import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.WorldType;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;

import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.injector.StructureCache;
import com.comphenix.protocol.reflect.EquivalentConverter;
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.ImmutableDetector;
import com.comphenix.protocol.reflect.cloning.OptionalCloner;
import com.comphenix.protocol.reflect.cloning.SerializableCloner;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.StreamSerializer;
import com.comphenix.protocol.wrappers.BlockPosition;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.ChunkCoordIntPair;
import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.EnumWrappers.ChatType;
import com.comphenix.protocol.wrappers.EnumWrappers.ChatVisibility;
import com.comphenix.protocol.wrappers.EnumWrappers.ClientCommand;
import com.comphenix.protocol.wrappers.EnumWrappers.CombatEventType;
import com.comphenix.protocol.wrappers.EnumWrappers.Difficulty;
import com.comphenix.protocol.wrappers.EnumWrappers.Direction;
import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction;
import com.comphenix.protocol.wrappers.EnumWrappers.EnumConverter;
import com.comphenix.protocol.wrappers.EnumWrappers.Hand;
import com.comphenix.protocol.wrappers.EnumWrappers.ItemSlot;
import com.comphenix.protocol.wrappers.EnumWrappers.NativeGameMode;
import com.comphenix.protocol.wrappers.EnumWrappers.Particle;
import com.comphenix.protocol.wrappers.EnumWrappers.PlayerAction;
import com.comphenix.protocol.wrappers.EnumWrappers.PlayerDigType;
import com.comphenix.protocol.wrappers.EnumWrappers.PlayerInfoAction;
import com.comphenix.protocol.wrappers.EnumWrappers.ResourcePackStatus;
import com.comphenix.protocol.wrappers.EnumWrappers.ScoreboardAction;
import com.comphenix.protocol.wrappers.EnumWrappers.SoundCategory;
import com.comphenix.protocol.wrappers.EnumWrappers.TitleAction;
import com.comphenix.protocol.wrappers.EnumWrappers.WorldBorderAction;
import com.comphenix.protocol.wrappers.MultiBlockChangeInfo;
import com.comphenix.protocol.wrappers.PlayerInfoData;
import com.comphenix.protocol.wrappers.WrappedAttribute;
import com.comphenix.protocol.wrappers.WrappedBlockData;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedServerPing;
import com.comphenix.protocol.wrappers.WrappedStatistic;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
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.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.UnpooledByteBufAllocator;

/**
 * Represents a Minecraft packet indirectly.
 * 
 * @author Kristian
 */
public class PacketContainer implements Serializable {
    private static final long serialVersionUID = 3;

    protected PacketType type;
    protected transient Object handle;

    // Current structure modifier
    protected transient StructureModifier<Object> structureModifier;

    // Support for serialization
    private static ConcurrentMap<Class<?>, Method> writeMethods = Maps.newConcurrentMap();
    private static ConcurrentMap<Class<?>, Method> readMethods = Maps.newConcurrentMap();

    // Used to clone packets
    private static final AggregateCloner DEEP_CLONER = AggregateCloner.newBuilder()
            .instanceProvider(DefaultInstances.DEFAULT).andThen(BukkitCloner.class).andThen(ImmutableDetector.class)
            .andThen(OptionalCloner.class).andThen(CollectionCloner.class)
            .andThen(getSpecializedDeepClonerFactory()).build();

    private static final AggregateCloner SHALLOW_CLONER = AggregateCloner.newBuilder()
            .instanceProvider(DefaultInstances.DEFAULT).andThen(new Function<BuilderParameters, Cloner>() {
                @Override
                public Cloner apply(@Nullable BuilderParameters param) {
                    if (param == null)
                        throw new IllegalArgumentException("Cannot be NULL.");

                    return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {
                        {
                            // Use a default writer with no concept of cloning
                            writer = new ObjectWriter();
                        }
                    };
                }
            }).build();

    // Packets that cannot be cloned by our default deep cloner
    private static final Set<PacketType> CLONING_UNSUPPORTED = Sets
            .newHashSet(PacketType.Play.Server.UPDATE_ATTRIBUTES, PacketType.Status.Server.SERVER_INFO);

    /**
     * Creates a packet container for a new packet.
     * <p>
     * Deprecated: Use {@link #PacketContainer(PacketType)} instead.
     * @param id - ID of the packet to create.
     */
    @Deprecated
    public PacketContainer(int id) {
        this(PacketType.findLegacy(id), StructureCache.newPacket(PacketType.findLegacy(id)));
    }

    /**
     * Creates a packet container for an existing packet.
     * @param id - ID of the given packet.
     * @param handle - contained packet.
     * @deprecated Use {@link #PacketContainer(PacketType, Object)} instead
     */
    @Deprecated
    public PacketContainer(int id, Object handle) {
        this(PacketType.findLegacy(id), handle);
    }

    /**
     * Creates a packet container for an existing packet.
     * @param id - ID of the given packet.
     * @param handle - contained packet.
     * @param structure - structure modifier.
     * @deprecated Use {@link #PacketContainer(PacketType, Object, StructureModifier)} instead
     */
    @Deprecated
    public PacketContainer(int id, Object handle, StructureModifier<Object> structure) {
        this(PacketType.findLegacy(id), handle, structure);
    }

    /**
     * Creates a packet container for a new packet.
     * @param type - the type of the packet to create.
     */
    public PacketContainer(PacketType type) {
        this(type, StructureCache.newPacket(type));
    }

    /**
     * Creates a packet container for an existing packet.
     * @param type - Type of the given packet.
     * @param handle - contained packet.
     */
    public PacketContainer(PacketType type, Object handle) {
        this(type, handle, StructureCache.getStructure(type).withTarget(handle));
    }

    /**
     * Creates a packet container for an existing packet.
     * @param type - Type of the given packet.
     * @param handle - contained packet.
     * @param structure - structure modifier.
     */
    public PacketContainer(PacketType type, Object handle, StructureModifier<Object> structure) {
        if (handle == null)
            throw new IllegalArgumentException("handle cannot be null.");
        if (type == null)
            throw new IllegalArgumentException("type cannot be null.");

        this.type = type;
        this.handle = handle;
        this.structureModifier = structure;
    }

    /**
     * Construct a new packet container from a given handle.
     * @param packet - the NMS packet.
     * @return The packet container.
     */
    public static PacketContainer fromPacket(Object packet) {
        PacketType type = PacketType.fromClass(packet.getClass());
        return new PacketContainer(type, packet);
    }

    /**
     * For serialization.
     */
    protected PacketContainer() {
    }

    /**
     * Retrieves the underlying Minecraft packet.
     * @return Underlying Minecraft packet.
     */
    public Object getHandle() {
        return handle;
    }

    /**
     * Retrieves the generic structure modifier for this packet.
     * @return Structure modifier.
     */
    public StructureModifier<Object> getModifier() {
        return structureModifier;
    }

    /**
     * Retrieves a read/write structure for every field with the given type.
     * @param <T> Type
     * @param primitiveType - the type to find.
     * @return A modifier for this specific type.
     */
    public <T> StructureModifier<T> getSpecificModifier(Class<T> primitiveType) {
        return structureModifier.withType(primitiveType);
    }

    /**
     * Retrieves a read/write structure for every byte field.
     * @return A modifier for every byte field.
     */
    public StructureModifier<Byte> getBytes() {
        return structureModifier.withType(byte.class);
    }

    /**
     * Retrieves a read/write structure for every boolean field.
     * @return A modifier for every boolean field.
     */
    public StructureModifier<Boolean> getBooleans() {
        return structureModifier.withType(boolean.class);
    }

    /**
     * Retrieves a read/write structure for every short field.
     * @return A modifier for every short field.
     */
    public StructureModifier<Short> getShorts() {
        return structureModifier.withType(short.class);
    }

    /**
     * Retrieves a read/write structure for every integer field.
     * @return A modifier for every integer field.
     */
    public StructureModifier<Integer> getIntegers() {
        return structureModifier.withType(int.class);
    }

    /**
     * Retrieves a read/write structure for every long field.
     * @return A modifier for every long field.
     */
    public StructureModifier<Long> getLongs() {
        return structureModifier.withType(long.class);
    }

    /**
     * Retrieves a read/write structure for every float field.
     * @return A modifier for every float field.
     */
    public StructureModifier<Float> getFloat() {
        return structureModifier.withType(float.class);
    }

    /**
     * Retrieves a read/write structure for every double field.
     * @return A modifier for every double field.
     */
    public StructureModifier<Double> getDoubles() {
        return structureModifier.withType(double.class);
    }

    /**
     * Retrieves a read/write structure for every String field.
     * @return A modifier for every String field.
     */
    public StructureModifier<String> getStrings() {
        return structureModifier.withType(String.class);
    }

    /**
     * Retrieves a read/write structure for every UUID field.
     * @return A modifier for every UUID field.
     */
    public StructureModifier<UUID> getUUIDs() {
        return structureModifier.withType(UUID.class);
    }

    /**
     * Retrieves a read/write structure for every String array field.
     * @return A modifier for every String array field.
     */
    public StructureModifier<String[]> getStringArrays() {
        return structureModifier.withType(String[].class);
    }

    /**
     * Retrieves a read/write structure for every byte array field.
     * @return A modifier for every byte array field.
     */
    public StructureModifier<byte[]> getByteArrays() {
        return structureModifier.withType(byte[].class);
    }

    /**
     * Retrieve a serializer for reading and writing ItemStacks stored in a byte array.
     * @return A instance of the serializer.
     */
    public StreamSerializer getByteArraySerializer() {
        return new StreamSerializer();
    }

    /**
     * Retrieves a read/write structure for every int array field.
     * @return A modifier for every int array field.
     */
    public StructureModifier<int[]> getIntegerArrays() {
        return structureModifier.withType(int[].class);
    }

    /**
     * Retrieves a read/write structure for ItemStack.
     * <p>
     * This modifier will automatically marshal between the Bukkit ItemStack and the
     * internal Minecraft ItemStack.
     * @return A modifier for ItemStack fields.
     */
    public StructureModifier<ItemStack> getItemModifier() {
        // Convert to and from the Bukkit wrapper
        return structureModifier.<ItemStack>withType(MinecraftReflection.getItemStackClass(),
                BukkitConverters.getItemStackConverter());
    }

    /**
     * Retrieves a read/write structure for arrays of ItemStacks.
     * <p>
     * This modifier will automatically marshal between the Bukkit ItemStack and the
     * internal Minecraft ItemStack.
     * @return A modifier for ItemStack array fields.
     */
    public StructureModifier<ItemStack[]> getItemArrayModifier() {
        // Convert to and from the Bukkit wrapper
        return structureModifier.<ItemStack[]>withType(MinecraftReflection.getItemStackArrayClass(),
                BukkitConverters.getIgnoreNull(new ItemStackArrayConverter()));
    }

    /**
     * Retrieves a read/write structure for lists of ItemStacks.
     * <p>
     * This modifier will automatically marshal between the Bukkit ItemStack and the
     * internal Minecraft ItemStack.
     * @return A modifier for ItemStack list fields.
     */
    public StructureModifier<List<ItemStack>> getItemListModifier() {
        // Convert to and from the Bukkit wrapper
        return structureModifier.withType(List.class, BukkitConverters.getListConverter(
                MinecraftReflection.getItemStackClass(), BukkitConverters.getItemStackConverter()));
    }

    /**
     * Retrieve a read/write structure for maps of statistics.
     * <p>
     * Note that you must write back the changed map to persist it.
     * @return A modifier for maps of statistics.
     */
    public StructureModifier<Map<WrappedStatistic, Integer>> getStatisticMaps() {
        return structureModifier.withType(Map.class, BukkitConverters.<WrappedStatistic, Integer>getMapConverter(
                MinecraftReflection.getStatisticClass(), BukkitConverters.getWrappedStatisticConverter()));
    }

    /**
     * Retrieves a read/write structure for the world type enum.
     * <p>
     * This modifier will automatically marshal between the Bukkit world type and the
     * internal Minecraft world type.
     * @return A modifier for world type fields.
     */
    public StructureModifier<WorldType> getWorldTypeModifier() {
        // Convert to and from the Bukkit wrapper
        return structureModifier.<WorldType>withType(MinecraftReflection.getWorldTypeClass(),
                BukkitConverters.getWorldTypeConverter());
    }

    /**
     * Retrieves a read/write structure for data watchers.
     * @return A modifier for data watchers.
     */
    public StructureModifier<WrappedDataWatcher> getDataWatcherModifier() {
        // Convert to and from the Bukkit wrapper
        return structureModifier.<WrappedDataWatcher>withType(MinecraftReflection.getDataWatcherClass(),
                BukkitConverters.getDataWatcherConverter());
    }

    /**
     * Retrieves a read/write structure for entity objects.
     * <p>
     * Note that entities are transmitted by integer ID, and the type may not be enough
     * to distinguish between entities and other values. Thus, this structure modifier
     * MAY return null or invalid entities for certain fields. Using the correct index
     * is essential.
     * 
     * @param world - the world each entity is currently occupying.
     * @return A modifier entity types.
     */
    public StructureModifier<Entity> getEntityModifier(@Nonnull World world) {
        Preconditions.checkNotNull(world, "world cannot be NULL.");
        // Convert to and from the Bukkit wrapper
        return structureModifier.<Entity>withType(int.class, BukkitConverters.getEntityConverter(world));
    }

    /**
     * Retrieves a read/write structure for entity objects.
     * <p>
     * Note that entities are transmitted by integer ID, and the type may not be enough
     * to distinguish between entities and other values. Thus, this structure modifier
     * MAY return null or invalid entities for certain fields. Using the correct index
     * is essential.
     * 
     * @param event - the original packet event.
     * @return A modifier entity types.
     */
    public StructureModifier<Entity> getEntityModifier(@Nonnull PacketEvent event) {
        Preconditions.checkNotNull(event, "event cannot be NULL.");
        return getEntityModifier(event.getPlayer().getWorld());
    }

    /**
     * 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.
     */
    public StructureModifier<BlockPosition> getBlockPositionModifier() {
        // Convert to and from the Bukkit wrapper
        return structureModifier.withType(MinecraftReflection.getBlockPositionClass(),
                BlockPosition.getConverter());
    }

    /**
     * Retrieves a read/write structure for wrapped ChunkCoordIntPairs.
     * @return A modifier for ChunkCoordIntPair.
     */
    public StructureModifier<ChunkCoordIntPair> getChunkCoordIntPairs() {
        // Allow access to the NBT class in packet 130
        return structureModifier.withType(MinecraftReflection.getChunkCoordIntPair(),
                ChunkCoordIntPair.getConverter());
    }

    /**
     * Retrieves a read/write structure for NBT classes.
     * @return A modifier for NBT classes.
     */
    public StructureModifier<NbtBase<?>> getNbtModifier() {
        // Allow access to the NBT class in packet 130
        return structureModifier.withType(MinecraftReflection.getNBTBaseClass(),
                BukkitConverters.getNbtConverter());
    }

    /**
     * Retrieves a read/write structure for lists of NBT classes.
     * @return A modifier for lists of NBT classes.
     */
    public StructureModifier<List<NbtBase<?>>> getListNbtModifier() {
        // Convert to and from the ProtocolLib wrapper
        return structureModifier.withType(Collection.class, BukkitConverters
                .getListConverter(MinecraftReflection.getNBTBaseClass(), BukkitConverters.getNbtConverter()));
    }

    /**
     * Retrieves a read/write structure for Vectors.
     * @return A modifier for Vectors.
     */
    public StructureModifier<Vector> getVectors() {
        // Automatically marshal between Vec3d and the Bukkit wrapper
        return structureModifier.withType(MinecraftReflection.getVec3DClass(),
                BukkitConverters.getVectorConverter());
    }

    /**
     * Retrieves a read/write structure for collections of attribute snapshots.
     * <p>
     * This modifier will automatically marshal between the visible ProtocolLib WrappedAttribute and the
     * internal Minecraft AttributeSnapshot.
     * @return A modifier for AttributeSnapshot collection fields.
     */
    public StructureModifier<List<WrappedAttribute>> getAttributeCollectionModifier() {
        // Convert to and from the ProtocolLib wrapper
        return structureModifier.withType(Collection.class, BukkitConverters.getListConverter(
                MinecraftReflection.getAttributeSnapshotClass(), BukkitConverters.getWrappedAttributeConverter()));
    }

    /**
     * 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(MinecraftReflection.getChunkPositionClass(), ChunkPosition.getConverter()));
    }

    /**
     * Retrieves a read/write structure for collections of chunk positions.
     * <p>
     * This modifier will automatically marshal between the visible ProtocolLib BlockPosition and the
     * internal Minecraft BlockPosition.
     *
     * @return A modifier for ChunkPosition list fields.
     */
    public StructureModifier<List<BlockPosition>> getBlockPositionCollectionModifier() {
        // Convert to and from the ProtocolLib wrapper
        return structureModifier.withType(Collection.class, BukkitConverters
                .getListConverter(MinecraftReflection.getBlockPositionClass(), BlockPosition.getConverter()));
    }

    /**
     * Retrieves a read/write structure for collections of watchable objects.
     * <p>
     * This modifier will automatically marshal between the visible WrappedWatchableObject and the
     * internal Minecraft WatchableObject.
     * @return A modifier for watchable object list fields.
     */
    public StructureModifier<List<WrappedWatchableObject>> getWatchableCollectionModifier() {
        // Convert to and from the ProtocolLib wrapper
        return structureModifier.withType(Collection.class, BukkitConverters.getListConverter(
                MinecraftReflection.getDataWatcherItemClass(), BukkitConverters.getWatchableObjectConverter()));
    }

    /**
     * Retrieves a read/write structure for block fields.
     * <p>
     * This modifier will automatically marshal between Material and the
     * internal Minecraft Block.
     * @return A modifier for GameProfile fields.
     */
    public StructureModifier<Material> getBlocks() {
        // Convert to and from the Bukkit wrapper
        return structureModifier.<Material>withType(MinecraftReflection.getBlockClass(),
                BukkitConverters.getBlockConverter());
    }

    /**
     * Retrieves a read/write structure for game profiles in Minecraft 1.7.2.
     * <p>
     * This modifier will automatically marshal between WrappedGameProfile and the
     * internal Minecraft GameProfile.
     * @return A modifier for GameProfile fields.
     */
    public StructureModifier<WrappedGameProfile> getGameProfiles() {
        // Convert to and from the Bukkit wrapper
        return structureModifier.<WrappedGameProfile>withType(MinecraftReflection.getGameProfileClass(),
                BukkitConverters.getWrappedGameProfileConverter());
    }

    /**
     * Retrieves a read/write structure for BlockData in Minecraft 1.8.
     * <p>
     * This modifier will automatically marshal between WrappedBlockData and the
     * internal Minecraft IBlockData.
     * @return A modifier for BlockData fields.
     */
    public StructureModifier<WrappedBlockData> getBlockData() {
        // Convert to and from our wrapper
        return structureModifier.<WrappedBlockData>withType(MinecraftReflection.getIBlockDataClass(),
                BukkitConverters.getWrappedBlockDataConverter());
    }

    /**
     * Retrieves a read/write structure for MultiBlockChangeInfo arrays in Minecraft 1.8.
     * <p>
     * This modifier will automatically marshal between MultiBlockChangeInfo and the
     * internal Minecraft MultiBlockChangeInfo.
     * @return A modifier for BlockData fields.
     */
    public StructureModifier<MultiBlockChangeInfo[]> getMultiBlockChangeInfoArrays() {
        ChunkCoordIntPair chunk = getChunkCoordIntPairs().read(0);

        // Convert to and from our wrapper
        return structureModifier.<MultiBlockChangeInfo[]>withType(
                MinecraftReflection.getMultiBlockChangeInfoArrayClass(),
                MultiBlockChangeInfo.getArrayConverter(chunk));
    }

    /**
     * Retrieves a read/write structure for chat components in Minecraft 1.7.2.
     * <p>
     * This modifier will automatically marshal between WrappedChatComponent and the
     * internal Minecraft IChatBaseComponent.
     * @return A modifier for ChatComponent fields.
     */
    public StructureModifier<WrappedChatComponent> getChatComponents() {
        // Convert to and from the Bukkit wrapper
        return structureModifier.<WrappedChatComponent>withType(MinecraftReflection.getIChatBaseComponentClass(),
                BukkitConverters.getWrappedChatComponentConverter());
    }

    /**
     * Retrieves a read/write structure for arrays of chat components.
     * <p>
     * This modifier will automatically marshal between WrappedChatComponent and the
     * internal Minecraft IChatBaseComponent (1.9.2 and below) or the internal
     * NBTCompound (1.9.4 and up).
     * <p>
     * Note that in 1.9.4 and up this modifier only works properly with sign
     * tile entities.
     * @return A modifier for ChatComponent array fields.
     */
    public StructureModifier<WrappedChatComponent[]> getChatComponentArrays() {
        // Convert to and from the Bukkit wrapper
        return structureModifier.<WrappedChatComponent[]>withType(ComponentArrayConverter.getGenericType(),
                BukkitConverters.getIgnoreNull(new ComponentArrayConverter()));
    }

    /**
     * Retrieve a read/write structure for the ServerPing fields in the following packet: <br>
     * <ul>
     *   <li>{@link PacketType.Status.Server#OUT_SERVER_INFO}
     * </ul>
     * @return A modifier for ServerPing fields.
     */
    public StructureModifier<WrappedServerPing> getServerPings() {
        // Convert to and from the wrapper
        return structureModifier.<WrappedServerPing>withType(MinecraftReflection.getServerPingClass(),
                BukkitConverters.getWrappedServerPingConverter());
    }

    /**
     * Retrieve a read/write structure for the PlayerInfoData list fields in the following packet: <br>
     * <ul>
     *   <li>{@link PacketType.Play.Server#PLAYER_INFO}
     * </ul>
     * @return A modifier for PlayerInfoData list fields.
     */
    public StructureModifier<List<PlayerInfoData>> getPlayerInfoDataLists() {
        // Convert to and from the ProtocolLib wrapper
        return structureModifier.withType(Collection.class, BukkitConverters
                .getListConverter(MinecraftReflection.getPlayerInfoDataClass(), PlayerInfoData.getConverter()));
    }

    /**
     * Retrieve a read/write structure for the Protocol enum in 1.7.2.
     * @return A modifier for Protocol enum fields.
     */
    public StructureModifier<Protocol> getProtocols() {
        // Convert to and from the wrapper
        return structureModifier.<Protocol>withType(EnumWrappers.getProtocolClass(),
                EnumWrappers.getProtocolConverter());
    }

    /**
     * Retrieve a read/write structure for the ClientCommand enum in 1.7.2.
     * @return A modifier for ClientCommand enum fields.
     */
    public StructureModifier<ClientCommand> getClientCommands() {
        // Convert to and from the wrapper
        return structureModifier.<ClientCommand>withType(EnumWrappers.getClientCommandClass(),
                EnumWrappers.getClientCommandConverter());
    }

    /**
     * Retrieve a read/write structure for the ChatVisibility enum in 1.7.2.
     * @return A modifier for ChatVisibility enum fields.
     */
    public StructureModifier<ChatVisibility> getChatVisibilities() {
        // Convert to and from the wrapper
        return structureModifier.<ChatVisibility>withType(EnumWrappers.getChatVisibilityClass(),
                EnumWrappers.getChatVisibilityConverter());
    }

    /**
     * Retrieve a read/write structure for the Difficulty enum in 1.7.2.
     * @return A modifier for Difficulty enum fields.
     */
    public StructureModifier<Difficulty> getDifficulties() {
        // Convert to and from the wrapper
        return structureModifier.<Difficulty>withType(EnumWrappers.getDifficultyClass(),
                EnumWrappers.getDifficultyConverter());
    }

    /**
     * Retrieve a read/write structure for the EntityUse enum in 1.7.2.
     * @return A modifier for EntityUse enum fields.
     */
    public StructureModifier<EntityUseAction> getEntityUseActions() {
        // Convert to and from the wrapper
        return structureModifier.<EntityUseAction>withType(EnumWrappers.getEntityUseActionClass(),
                EnumWrappers.getEntityUseActionConverter());
    }

    /**
     * Retrieve a read/write structure for the NativeGameMode enum in 1.7.2.
     * @return A modifier for NativeGameMode enum fields.
     */
    public StructureModifier<NativeGameMode> getGameModes() {
        // Convert to and from the wrapper
        return structureModifier.<NativeGameMode>withType(EnumWrappers.getGameModeClass(),
                EnumWrappers.getGameModeConverter());
    }

    /**
     * Retrieve a read/write structure for the ResourcePackStatus enum in 1.8.
     * @return A modifier for ResourcePackStatus enum fields.
     */
    public StructureModifier<ResourcePackStatus> getResourcePackStatus() {
        // Convert to and from the wrapper
        return structureModifier.<ResourcePackStatus>withType(EnumWrappers.getResourcePackStatusClass(),
                EnumWrappers.getResourcePackStatusConverter());
    }

    /**
     * Retrieve a read/write structure for the PlayerInfo enum in 1.8.
     * @return A modifier for PlayerInfoAction enum fields.
     */
    public StructureModifier<PlayerInfoAction> getPlayerInfoAction() {
        // Convert to and from the wrapper
        return structureModifier.<PlayerInfoAction>withType(EnumWrappers.getPlayerInfoActionClass(),
                EnumWrappers.getPlayerInfoActionConverter());
    }

    /**
     * Retrieve a read/write structure for the TitleAction enum in 1.8.
     * @return A modifier for TitleAction enum fields.
     */
    public StructureModifier<TitleAction> getTitleActions() {
        // Convert to and from the wrapper
        return structureModifier.<TitleAction>withType(EnumWrappers.getTitleActionClass(),
                EnumWrappers.getTitleActionConverter());
    }

    /**
     * Retrieve a read/write structure for the WorldBorderAction enum in 1.8.
     * @return A modifier for WorldBorderAction enum fields.
     */
    public StructureModifier<WorldBorderAction> getWorldBorderActions() {
        // Convert to and from the wrapper
        return structureModifier.<WorldBorderAction>withType(EnumWrappers.getWorldBorderActionClass(),
                EnumWrappers.getWorldBorderActionConverter());
    }

    /**
     * Retrieve a read/write structure for the CombatEventType enum in 1.8.
     * @return A modifier for CombatEventType enum fields.
     */
    public StructureModifier<CombatEventType> getCombatEvents() {
        // Convert to and from the wrapper
        return structureModifier.<CombatEventType>withType(EnumWrappers.getCombatEventTypeClass(),
                EnumWrappers.getCombatEventTypeConverter());
    }

    /**
     * Retrieve a read/write structure for the PlayerDigType enum in 1.8.
     * @return A modifier for PlayerDigType enum fields.
     */
    public StructureModifier<PlayerDigType> getPlayerDigTypes() {
        // Convert to and from the wrapper
        return structureModifier.<PlayerDigType>withType(EnumWrappers.getPlayerDigTypeClass(),
                EnumWrappers.getPlayerDiggingActionConverter());
    }

    /**
     * Retrieve a read/write structure for the PlayerAction enum in 1.8.
     * @return A modifier for PlayerAction enum fields.
     */
    public StructureModifier<PlayerAction> getPlayerActions() {
        // Convert to and from the wrapper
        return structureModifier.<PlayerAction>withType(EnumWrappers.getPlayerActionClass(),
                EnumWrappers.getEntityActionConverter());
    }

    /**
     * Retrieve a read/write structure for the ScoreboardAction enum in 1.8.
     * @return A modifier for ScoreboardAction enum fields.
     */
    public StructureModifier<ScoreboardAction> getScoreboardActions() {
        // Convert to and from the wrapper
        return structureModifier.<ScoreboardAction>withType(EnumWrappers.getScoreboardActionClass(),
                EnumWrappers.getUpdateScoreActionConverter());
    }

    /**
     * Retrieve a read/write structure for the Particle enum in 1.8.
     * @return A modifier for Particle enum fields.
     */
    public StructureModifier<Particle> getParticles() {
        // Convert to and from the wrapper
        return structureModifier.<Particle>withType(EnumWrappers.getParticleClass(),
                EnumWrappers.getParticleConverter());
    }

    /**
     * Retrieve a read/write structure for the MobEffectList class in 1.9.
     * @return A modifier for MobEffectList fields.
     */
    public StructureModifier<PotionEffectType> getEffectTypes() {
        // Convert to and from Bukkit
        return structureModifier.<PotionEffectType>withType(MinecraftReflection.getMobEffectListClass(),
                BukkitConverters.getEffectTypeConverter());
    }

    /**
     * Retrieve a read/write structure for the SoundCategory enum in 1.9.
     * @return A modifier for SoundCategory enum fields.
     */
    public StructureModifier<SoundCategory> getSoundCategories() {
        // Convert to and from the enums
        return structureModifier.<SoundCategory>withType(EnumWrappers.getSoundCategoryClass(),
                EnumWrappers.getSoundCategoryConverter());
    }

    /**
     * Retrieve a read/write structure for the SoundEffect enum in 1.9.
     * @return A modifier for SoundEffect enum fields.
     */
    public StructureModifier<Sound> getSoundEffects() {
        // Convert to and from Bukkit
        return structureModifier.<Sound>withType(MinecraftReflection.getSoundEffectClass(),
                BukkitConverters.getSoundConverter());
    }

    /**
     * Retrieve a read/write structure for the ItemSlot enum in 1.9.
     * @return A modifier for ItemSlot enum fields.
     */
    public StructureModifier<ItemSlot> getItemSlots() {
        return structureModifier.<ItemSlot>withType(EnumWrappers.getItemSlotClass(),
                EnumWrappers.getItemSlotConverter());
    }

    /**
     * Retrieve a read/write structure for the Hand enum in 1.9.
     * @return A modifier for Hand enum fields.
     */
    public StructureModifier<Hand> getHands() {
        return structureModifier.<Hand>withType(EnumWrappers.getHandClass(), EnumWrappers.getHandConverter());
    }

    /**
     * Retrieve a read/write structure for the Direction enum in 1.10.
     * @return A modifier for Direction enum fields.
     */
    public StructureModifier<Direction> getDirections() {
        return structureModifier.<Direction>withType(EnumWrappers.getDirectionClass(),
                EnumWrappers.getDirectionConverter());
    }

    /**
     * Retrieve a read/write structure for the ChatType enum in 1.12.
     * @return A modifier for ChatType enum fields.
     */
    public StructureModifier<ChatType> getChatTypes() {
        return structureModifier.<ChatType>withType(EnumWrappers.getChatTypeClass(),
                EnumWrappers.getChatTypeConverter());
    }

    /**
     * Retrieve a read/write structure for an enum. This allows for the use of
     * user-created enums that may not exist in ProtocolLib. The specific (user
     * created) enum constants must match up perfectly with their generic (NMS)
     * counterparts.
     * 
     * @param enumClass The specific Enum class
     * @param nmsClass The generic Enum class
     * @return The modifier
     */
    public <T extends Enum<T>> StructureModifier<T> getEnumModifier(Class<T> enumClass, Class<?> nmsClass) {
        return structureModifier.<T>withType(nmsClass, new EnumConverter<T>(enumClass));
    }

    /**
     * Retrieve a read/write structure for an enum. This method is for convenience,
     * see {@link #getEnumModifier(Class, Class)} for more information.
     * 
     * @param enumClass The specific Enum class
     * @param index Index of the generic Enum
     * @return The modifier
     * @see #getEnumModifier(Class, Class)
     */
    public <T extends Enum<T>> StructureModifier<T> getEnumModifier(Class<T> enumClass, int index) {
        return getEnumModifier(enumClass, structureModifier.getField(index).getType());
    }

    /**
     * Retrieves the ID of this packet.
     * <p>
     * Deprecated: Use {@link #getType()} instead.
     * @return Packet ID.
     */
    @Deprecated
    public int getID() {
        return type.getLegacyId();
    }

    /**
     * Retrieve the packet type of this packet.
     * @return The packet type.
     */
    public PacketType getType() {
        return type;
    }

    /**
     * Create a shallow copy of the current packet.
     * <p>
     * This merely writes the content of each field to the new class directly,
     * without performing any expensive copies.
     * 
     * @return A shallow copy of the current packet.
     */
    public PacketContainer shallowClone() {
        Object clonedPacket = SHALLOW_CLONER.clone(getHandle());
        return new PacketContainer(getType(), clonedPacket);
    }

    /**
     * Create a deep copy of the current packet.
     * <p>
     * This will perform a full copy of the entire object tree, only skipping
     * known immutable objects and primitive types.
     * <p>
     * Note that the inflated buffers in packet 51 and 56 will be copied directly to save memory.
     * 
     * @return A deep copy of the current packet.
     */
    public PacketContainer deepClone() {
        Object clonedPacket = null;

        // Fall back on the alternative (but slower) method of reading and writing back the packet
        if (CLONING_UNSUPPORTED.contains(type)) {
            clonedPacket = SerializableCloner.clone(this).getHandle();
        } else {
            clonedPacket = DEEP_CLONER.clone(getHandle());
        }
        return new PacketContainer(getType(), clonedPacket);
    }

    // To save space, we'll skip copying the inflated buffers in packet 51 and 56
    private static Function<BuilderParameters, Cloner> getSpecializedDeepClonerFactory() {
        // Look at what you've made me do Java, look at it!!
        return new Function<BuilderParameters, Cloner>() {
            @Override
            public Cloner apply(@Nullable BuilderParameters param) {
                return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {
                    {
                        this.writer = new ObjectWriter() {
                            @Override
                            protected void transformField(StructureModifier<Object> modifierSource,
                                    StructureModifier<Object> modifierDest, int fieldIndex) {
                                // No need to clone inflated buffers
                                if (modifierSource.getField(fieldIndex).getName().startsWith("inflatedBuffer"))
                                    modifierDest.write(fieldIndex, modifierSource.read(fieldIndex));
                                else
                                    defaultTransform(modifierSource, modifierDest, getDefaultCloner(), fieldIndex);
                            };
                        };
                    }
                };
            }
        };
    }

    private void writeObject(ObjectOutputStream output) throws IOException {
        // Default serialization
        output.defaultWriteObject();

        // We'll take care of NULL packets as well
        output.writeBoolean(handle != null);

        try {
            if (MinecraftReflection.isUsingNetty()) {
                ByteBuf buffer = createPacketBuffer();
                MinecraftMethods.getPacketWriteByteBufMethod().invoke(handle, buffer);

                output.writeInt(buffer.readableBytes());
                buffer.readBytes(output, buffer.readableBytes());
            } else {
                // Call the write-method
                output.writeInt(-1);
                getMethodLazily(writeMethods, handle.getClass(), "write", DataOutput.class).invoke(handle,
                        new DataOutputStream(output));
            }

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

    private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
        // Default deserialization
        input.defaultReadObject();

        // Get structure modifier
        structureModifier = StructureCache.getStructure(type);

        // Don't read NULL packets
        if (input.readBoolean()) {

            // Create a default instance of the packet
            handle = StructureCache.newPacket(type);

            // Call the read method
            try {
                if (MinecraftReflection.isUsingNetty()) {
                    ByteBuf buffer = createPacketBuffer();
                    buffer.writeBytes(input, input.readInt());

                    MinecraftMethods.getPacketReadByteBufMethod().invoke(handle, buffer);
                } else {
                    if (input.readInt() != -1)
                        throw new IllegalArgumentException("Cannot load a packet from 1.7.2 in 1.6.4.");

                    getMethodLazily(readMethods, handle.getClass(), "read", DataInput.class).invoke(handle,
                            new DataInputStream(input));
                }
            } 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);
            }

            // And we're done
            structureModifier = structureModifier.withTarget(handle);
        }
    }

    /**
     * Construct a new packet data serializer.
     * @return The packet data serializer.
     */
    public static ByteBuf createPacketBuffer() {
        ByteBuf buffer = UnpooledByteBufAllocator.DEFAULT.buffer();
        Class<?> packetSerializer = MinecraftReflection.getPacketDataSerializerClass();

        try {
            return (ByteBuf) packetSerializer.getConstructor(ByteBuf.class).newInstance(buffer);
        } catch (Exception e) {
            throw new RuntimeException("Cannot construct packet serializer.", e);
        }
    }

    // ---- Metadata
    // This map will only be initialized if it is actually used
    private Map<String, Object> metadata;

    /**
     * Gets the metadata value for a given key.
     * 
     * @param key Metadata key
     * @return Metadata value, or null if nonexistent.
     */
    public Object getMetadata(String key) {
        if (metadata != null) {
            return metadata.get(key);
        }

        return null;
    }

    /**
     * Adds metadata to this packet.
     * <p>
     * Note: Since metadata is lazily initialized, this may result in the creation of the metadata map.
     * 
     * @param key Metadata key
     * @param value Metadata value
     */
    public void addMetadata(String key, Object value) {
        if (metadata == null) {
            metadata = new HashMap<String, Object>();
        }

        metadata.put(key, value);
    }

    /**
     * Removes metadata from this packet.
     * <p>
     * Note: If this operation leaves the metadata map empty, the map will be set to null.
     * 
     * @param key Metadata key
     * @return The previous value, or null if nonexistant.
     */
    public Object removeMetadata(String key) {
        if (metadata != null) {
            Object value = metadata.remove(key);
            if (metadata.isEmpty()) {
                metadata = null;
            }

            return value;
        }

        return null;
    }

    /**
     * Whether or not this packet has metadata for a given key.
     * 
     * @param key Metadata key
     * @return True if this packet has metadata for the key, false if not.
     */
    public boolean hasMetadata(String key) {
        return metadata != null && metadata.containsKey(key);
    }

    /**
     * Retrieve the cached method concurrently.
     * @param lookup - a lazy lookup cache.
     * @param handleClass - class type of the current packet.
     * @param methodName - name of method to retrieve.
     * @param parameterClass - the one parameter type in the method.
     * @return Reflected method.
     */
    private Method getMethodLazily(ConcurrentMap<Class<?>, Method> lookup, Class<?> handleClass, String methodName,
            Class<?> parameterClass) {
        Method method = lookup.get(handleClass);

        // Atomic operation
        if (method == null) {
            Method initialized = FuzzyReflection.fromClass(handleClass).getMethod(FuzzyMethodContract.newBuilder()
                    .parameterCount(1).parameterDerivedOf(parameterClass).returnTypeVoid().build());
            method = lookup.putIfAbsent(handleClass, initialized);

            // Use our version if we succeeded
            if (method == null) {
                method = initialized;
            }
        }

        return method;
    }

    /**
     * Represents an equivalent converter for ItemStack arrays.
     * @author Kristian
     */
    private static class ItemStackArrayConverter implements EquivalentConverter<ItemStack[]> {
        final EquivalentConverter<ItemStack> stackConverter = BukkitConverters.getItemStackConverter();

        @Override
        public Object getGeneric(Class<?> genericType, ItemStack[] specific) {
            Class<?> nmsStack = MinecraftReflection.getItemStackClass();
            Object[] result = (Object[]) Array.newInstance(nmsStack, specific.length);

            // Unwrap every item
            for (int i = 0; i < result.length; i++) {
                result[i] = stackConverter.getGeneric(nmsStack, specific[i]);
            }
            return result;
        }

        @Override
        public ItemStack[] getSpecific(Object generic) {
            Object[] input = (Object[]) generic;
            ItemStack[] result = new ItemStack[input.length];

            // Add the wrapper
            for (int i = 0; i < result.length; i++) {
                result[i] = stackConverter.getSpecific(input[i]);
            }
            return result;
        }

        @Override
        public Class<ItemStack[]> getSpecificType() {
            return ItemStack[].class;
        }
    }

    /**
     * Represents an equivalent converter for ChatComponent arrays.
     * @author dmulloy2
     */
    private static class LegacyComponentConverter implements EquivalentConverter<WrappedChatComponent[]> {
        final EquivalentConverter<WrappedChatComponent> componentConverter = BukkitConverters
                .getWrappedChatComponentConverter();

        @Override
        public Object getGeneric(Class<?> genericType, WrappedChatComponent[] specific) {
            Class<?> nmsComponent = MinecraftReflection.getIChatBaseComponentClass();
            Object[] result = (Object[]) Array.newInstance(nmsComponent, specific.length);

            // Unwrap every item
            for (int i = 0; i < result.length; i++) {
                result[i] = componentConverter.getGeneric(nmsComponent, specific[i]);
            }
            return result;
        }

        @Override
        public WrappedChatComponent[] getSpecific(Object generic) {
            Object[] input = (Object[]) generic;
            WrappedChatComponent[] result = new WrappedChatComponent[input.length];

            // Add the wrapper
            for (int i = 0; i < result.length; i++) {
                result[i] = componentConverter.getSpecific(input[i]);
            }
            return result;
        }

        @Override
        public Class<WrappedChatComponent[]> getSpecificType() {
            return WrappedChatComponent[].class;
        }
    }

    /**
     * Converts from NBT to WrappedChatComponent arrays
     * @author dmulloy2
     */
    private static class NBTComponentConverter implements EquivalentConverter<WrappedChatComponent[]> {
        private EquivalentConverter<NbtBase<?>> nbtConverter = BukkitConverters.getNbtConverter();
        private final int lines = 4;

        @Override
        public WrappedChatComponent[] getSpecific(Object generic) {
            NbtBase<?> nbtBase = nbtConverter.getSpecific(generic);
            NbtCompound compound = (NbtCompound) nbtBase;

            WrappedChatComponent[] components = new WrappedChatComponent[lines];
            for (int i = 0; i < lines; i++) {
                if (compound.containsKey("Text" + (i + 1))) {
                    components[i] = WrappedChatComponent.fromJson(compound.getString("Text" + (i + 1)));
                } else {
                    components[i] = WrappedChatComponent.fromText("");
                }
            }

            return components;
        }

        @Override
        public Object getGeneric(Class<?> genericType, WrappedChatComponent[] specific) {
            NbtCompound compound = NbtFactory.ofCompound("");

            for (int i = 0; i < lines; i++) {
                WrappedChatComponent component;
                if (i < specific.length && specific[i] != null) {
                    component = specific[i];
                } else {
                    component = WrappedChatComponent.fromText("");
                }

                compound.put("Text" + (i + 1), component.getJson());
            }

            return nbtConverter.getGeneric(genericType, compound);
        }

        @Override
        public Class<WrappedChatComponent[]> getSpecificType() {
            return WrappedChatComponent[].class;
        }
    }

    /**
     * A delegated converter that supports NBT to Component Array and regular Component Array
     * @author dmulloy2
     */
    private static class ComponentArrayConverter implements EquivalentConverter<WrappedChatComponent[]> {
        private static final EquivalentConverter<WrappedChatComponent[]> DELEGATE;
        static {
            if (!PacketType.Play.Server.UPDATE_SIGN.isDeprecated()) {
                DELEGATE = new LegacyComponentConverter();
            } else {
                DELEGATE = new NBTComponentConverter();
            }
        }

        @Override
        public WrappedChatComponent[] getSpecific(Object generic) {
            return DELEGATE.getSpecific(generic);
        }

        @Override
        public Object getGeneric(Class<?> genericType, WrappedChatComponent[] specific) {
            return DELEGATE.getGeneric(genericType, specific);
        }

        @Override
        public Class<WrappedChatComponent[]> getSpecificType() {
            return DELEGATE.getSpecificType();
        }

        public static Class<?> getGenericType() {
            if (DELEGATE instanceof NBTComponentConverter) {
                return MinecraftReflection.getNBTCompoundClass();
            } else {
                return MinecraftReflection.getIChatBaseComponentArrayClass();
            }
        }
    }

    @Override
    public String toString() {
        return "PacketContainer[type=" + type + ", structureModifier=" + structureModifier + "]";
    }
}