protected void setProtocol(final Channel channel, final ByteBuf input, ProtocolVersion version)
     throws Exception {
   if (protocolSet) {
     return;
   }
   protocolSet = true;
   ProtocolStorage.setProtocolVersion(Utils.getNetworkManagerSocketAddress(channel), version);
   channel.pipeline().remove(ChannelHandlers.INITIAL_DECODER);
   pipelineBuilders.get(version).buildPipeLine(channel, version);
   input.readerIndex(0);
   channel.pipeline().firstContext().fireChannelRead(input);
 }
 @Override
 public void channelRead(final ChannelHandlerContext ctx, final Object inputObj) throws Exception {
   try {
     final ByteBuf input = (ByteBuf) inputObj;
     if (!input.isReadable()) {
       return;
     }
     final Channel channel = ctx.channel();
     receivedData.writeBytes(input);
     ProtocolVersion handshakeversion = ProtocolVersion.NOT_SET;
     receivedData.readerIndex(0);
     int firstbyte = receivedData.readUnsignedByte();
     switch (firstbyte) {
       case 0xFE:
         { // old ping
           try {
             if (receivedData.readableBytes() == 0) { // really old protocol probably
               scheduleTask(ctx, new Ping11ResponseTask(channel), 1000, TimeUnit.MILLISECONDS);
             } else if (receivedData.readUnsignedByte() == 1) {
               if (receivedData.readableBytes() == 0) {
                 // 1.5.2 probably
                 scheduleTask(
                     ctx, new Ping152ResponseTask(this, channel), 500, TimeUnit.MILLISECONDS);
               } else if ((receivedData.readUnsignedByte() == 0xFA)
                   && "MC|PingHost"
                       .equals(
                           new String(
                               Utils.toArray(
                                   receivedData.readBytes(receivedData.readUnsignedShort() * 2)),
                               StandardCharsets.UTF_16BE))) { // 1.6.*
                 receivedData.readUnsignedShort();
                 handshakeversion = ProtocolVersion.fromId(receivedData.readUnsignedByte());
               }
             }
           } catch (IndexOutOfBoundsException ex) {
           }
           break;
         }
       case 0x02:
         { // 1.6 or 1.5.2 handshake
           try {
             handshakeversion = ProtocolVersion.fromId(receivedData.readUnsignedByte());
           } catch (IndexOutOfBoundsException ex) {
           }
           break;
         }
       default:
         { // 1.7 or 1.8 handshake
           receivedData.readerIndex(0);
           ByteBuf data = getVarIntPrefixedData(receivedData);
           if (data != null) {
             handshakeversion = readNPHandshake(data);
           }
           break;
         }
     }
     // if we detected the protocol than we save it and process data
     if (handshakeversion != ProtocolVersion.NOT_SET) {
       setProtocol(channel, receivedData, handshakeversion);
     }
   } catch (Throwable t) {
     ctx.channel().close();
   } finally {
     ReferenceCountUtil.release(inputObj);
   }
 }
public class AsyncErrorLogger {

  private static final boolean enabled =
      Utils.getJavaPropertyValue(
          "protocolsupport.errlog.errlog", true, Converter.STRING_TO_BOOLEAN);
  private static final long maxFileSize =
      Utils.getJavaPropertyValue(
          "protocolsupport.errlog.maxsize", 1024L * 1024L * 20L, Converter.STRING_TO_LONG);
  private static final String filePath =
      Utils.getJavaPropertyValue(
          "protocolsupport.errlog.path",
          JavaPlugin.getPlugin(ProtocolSupport.class).getName() + "-errlog",
          Converter.NONE);

  public static final AsyncErrorLogger INSTANCE = new AsyncErrorLogger();

  private final ExecutorService executor =
      Executors.newSingleThreadExecutor(
          new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
              return new Thread(r, JavaPlugin.getPlugin(ProtocolSupport.class) + "-errlog-thread");
            }
          });

  public void start() {}

  public void stop() {
    executor.shutdown();
    try {
      executor.awaitTermination(10, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
    }
    synchronized (writer) {
      if (writer != null) {
        writer.close();
      }
    }
  }

  private PrintWriter writer;

  private AsyncErrorLogger() {
    if (!enabled) {
      stop();
      return;
    }
    try {
      File logfile = new File(filePath);
      if (logfile.length() > maxFileSize) {
        logfile.delete();
      }
      logfile.createNewFile();
      writer = new PrintWriter(new FileOutputStream(logfile, true));
    } catch (Exception e) {
      ProtocolSupport.logWarning("Unable to create error log");
      stop();
    }
  }

  public void log(final Throwable t, final Object... info) {
    if (executor.isShutdown()) {
      return;
    }
    try {
      executor.submit(
          new Runnable() {
            @Override
            public void run() {
              synchronized (writer) {
                writer.println(
                    "Error occured at "
                        + new SimpleDateFormat("yyyy-MM-dd-HH-mm", Locale.ROOT).format(new Date()));
                writer.println("Additional info: " + StringUtils.join(info, ", "));
                writer.println("Exception class: " + t.getClass().getName());
                writer.println("Exception message: " + t.getMessage());
                writer.println("Exception log:");
                t.printStackTrace(writer);
                writer.println();
              }
            }
          });
    } catch (RejectedExecutionException e) {
    }
  }
}