/*
Copyright 2013 Twitter, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package com.twitter.chill.protobuf;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

import com.google.protobuf.Message;

import java.lang.reflect.Method;

/**
 * Kryo serializer for Protobuf instances.
 *
 * Note that this class is not thread-safe. (Kryo itself is not thread
 * safe, so this shouldn't be a concern.)
 *
 * Use this with
 * addDefaultSerializer(Message.class, ProtobufSerializer.class)
 * It still helps to .register your instances so the full class name
 * does not need to be written.
 */
public class ProtobufSerializer extends Serializer<Message> {

  protected Method getParse(Class cls) throws Exception {
    return cls.getMethod("parseFrom", new Class[]{ byte[].class });
  }

  @Override
  public void write(Kryo kryo, Output output, Message mes) {
    byte[] ser = mes.toByteArray();
    output.writeInt(ser.length, true);
    output.writeBytes(ser);
  }

  @Override
  public Message read(Kryo kryo, Input input, Class<Message> pbClass) {
    try {
      int size = input.readInt(true);
      byte[] barr = new byte[size];
      input.readBytes(barr);
      return (Message)getParse(pbClass).invoke(null, barr);
    } catch (Exception e) {
      throw new RuntimeException("Could not create " + pbClass, e);
    }
  }
}


@@ -828,16 +828,42 @@
 .Method;
+%0Aimport java.util.HashMap;
 %0A%0A/**%0A *
@@ -1278,130 +1278,654 @@
 %0A%0A  
-protected Method getParse(Class cls) throws Exception %7B%0A    return cls.getMethod(%22parseFrom%22, new Class%5B%5D%7B byte%5B%5D.class %7D)
+/* This cache never clears, but only scales like the number of%0A   * classes in play, which should not be very large.%0A   * We can replace with a LRU if we start to see any issues.%0A   */%0A  final protected HashMap%3CClass, Method%3E methodCache = new HashMap%3CClass, Method%3E();%0A%0A  /**%0A   * This is slow, so we should cache to avoid killing perf:%0A   * See: http://www.jguru.com/faq/view.jsp?EID=246569%0A   */%0A  protected Method getParse(Class cls) throws Exception %7B%0A    Method meth = methodCache.get(cls);%0A    if (null == meth) %7B%0A      meth = cls.getMethod(%22parseFrom%22, new Class%5B%5D%7B byte%5B%5D.class %7D);%0A      methodCache.put(cls, meth);%0A    %7D%0A    return meth
 ;%0A  

Add a cache