content: package com.alecgorge.minecraft.jsonapi.packets.netty;
  
  import io.netty.channel.Channel;
  import io.netty.channel.ChannelFuture;
  import io.netty.channel.ChannelHandler;
  import io.netty.channel.ChannelHandlerContext;
  import io.netty.channel.ChannelInboundHandler;
  import io.netty.channel.ChannelInboundHandlerAdapter;
  import io.netty.channel.ChannelInitializer;
  
  import java.lang.reflect.Field;
  import java.lang.reflect.Method;
  import java.util.List;
  
  import com.comphenix.protocol.reflect.FuzzyReflection;
  import com.comphenix.protocol.reflect.VolatileField;
  import com.comphenix.protocol.utility.MinecraftReflection;
  import com.google.common.collect.Lists;
  
  public abstract class NettyInjector {   
      // The temporary player factory
      private List<VolatileField> bootstrapFields = Lists.newArrayList();
     
      // List of network managers
      private volatile List<Object> networkManagers;
      private boolean injected;
  
      private boolean closed;
  
      /**
       * Inject into the spigot connection class.
       */
      @SuppressWarnings("unchecked")
      public synchronized void inject() {
          if (injected)
              throw new IllegalStateException("Cannot inject twice.");
          try {
              FuzzyReflection fuzzyServer = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass());
              Method serverConnectionMethod = fuzzyServer.getMethodByParameters("getServerConnection", MinecraftReflection.getServerConnectionClass(), new Class[] {});
              
              // Get the server connection
              Object server = fuzzyServer.getSingleton();
              Object serverConnection = serverConnectionMethod.invoke(server);
              
              // Handle connected channels
              final ChannelInboundHandler endInitProtocol = new ChannelInitializer<Channel>() {
                  @Override
                  protected void initChannel(Channel channel) throws Exception {
                      try {
                          // This can take a while, so we need to stop the main thread from interfering
                          synchronized (networkManagers) {
                              injectChannel(channel);
                          }
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                  }
              };
              
              // This is executed before Minecraft's channel handler
              final ChannelInboundHandler beginInitProtocol = new ChannelInitializer<Channel>() {
                  @Override
                  protected void initChannel(Channel channel) throws Exception {
                      // Our only job is to add init protocol
                      channel.pipeline().addLast(endInitProtocol);
                  }
              };
              
              // Add our handler to newly created channels
              final ChannelHandler connectionHandler = new ChannelInboundHandlerAdapter() {
                  @Override
                  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                      Channel channel = (Channel) msg;
  
                      // Prepare to initialize ths channel
                      channel.pipeline().addFirst(beginInitProtocol);
                      ctx.fireChannelRead(msg);
                  }
              };
              
              // Get the current NetworkMananger list
              networkManagers = (List<Object>) FuzzyReflection.fromObject(serverConnection, true).
                  invokeMethod(null, "getNetworkManagers", List.class, serverConnection);
              
              // Insert ProtocolLib's connection interceptor
              bootstrapFields = getBootstrapFields(serverConnection);
              
              for (VolatileField field : bootstrapFields) {
                  final List<Object> list = (List<Object>) field.getValue();
       
                  // We don't have to override this list
                  if (list == networkManagers) {
                      continue;
                  }
                  
                  // Synchronize with each list before we attempt to replace them.
                  field.setValue(new BootstrapList(list, connectionHandler));
              }
  
              injected = true;
              
          } catch (Exception e) {
              throw new RuntimeException("Unable to inject channel futures.", e);
          }
      }
      
      /**
       * Invoked when a channel is ready to be injected.
       * @param channel - the channel to inject.
       */
      protected abstract void injectChannel(Channel channel);
      
      /**
       * Retrieve a list of every field with a list of channel futures.
       * @param serverConnection - the connection.
       * @return List of fields.
       */
      private List<VolatileField> getBootstrapFields(Object serverConnection) {
          List<VolatileField> result = Lists.newArrayList();
          
          // Find and (possibly) proxy every list
          for (Field field : FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(List.class)) {
              VolatileField volatileField = new VolatileField(field, serverConnection, true).toSynchronized();
              
              @SuppressWarnings("unchecked")
              List<Object> list = (List<Object>) volatileField.getValue();
              
              if (list.size() == 0 || list.get(0) instanceof ChannelFuture) {
                  result.add(volatileField);
              }
          }
          return result;
      }
      
      /**
       * Clean up any remaning injections.
       */
      public synchronized void close() {
          if (!closed) {
              closed = true;
  
              for (VolatileField field : bootstrapFields) {
                  Object value = field.getValue();
  
                  // Undo the processed channels, if any 
                  if (value instanceof BootstrapList) {
                      ((BootstrapList) value).close();
                  }
                  field.revertValue();
              }
          }
      }
  }
  