Javaオブジェクトに対する操作の記録と再生

元ネタ

What Kind of Differences?

Consider the following class. It defines an object that is able to record all the messages ever sent to it, and then playback those messages to another object.
以下のクラスは、オブジェクトに送られた全てのメッセージを記録し、他のオブジェクトで再生することが出来るよう、定義されている。

class VCR
  def initialize
    @messages = []
  end
  def method_missing(method, *args, &block)
    @messages << [method, args, block]
  end
  def play_back_to(obj)
    @messages.each do |method, args, block|
      obj.send(method, *args, &block)
    end
  end
end

http://onestepback.org/articles/10things/page017.html

Example Code

require 'src/vcr'

vcr = VCR.new
vcr.sub!(/Java/) { "Ruby" }
vcr.upcase!
vcr[11,5] = "Universe"
vcr << "!"

string = "Hello Java World"
puts string

vcr.play_back_to(string)
puts string

Output

Hello Java World
HELLO RUBY Universe!

http://onestepback.org/articles/10things/page018.html

VCR

import java.lang.reflect.*;
import java.util.*;

public class VCR<T> {
  class Message {
    final Method method;
    final Object[] args;
    Message(Method method, Object...args) {
      this.method = method;
      this.args = args;
    }
  }
  
  final List<Message> messages = new ArrayList<Message>();
  final T proxyInstance;
  
  @SuppressWarnings("unchecked")
  public VCR(Class<T> interfaceClass) {
    this.proxyInstance = createInstance(interfaceClass);
  }
  
  @SuppressWarnings("unchecked")
  T createInstance(Class<T>...interfaces) {
    return (T)Proxy.newProxyInstance(
      Thread.currentThread().getContextClassLoader(), interfaces,
      new InvocationHandler(){
        public Object invoke(
          Object proxy, Method method,Object[] args
        ) throws Throwable {
          messages.add(new Message(method, args));
          return null;
        }
    });
  }
  
  public T rec() {
    return this.proxyInstance;
  }

  public <TImpl extends T> TImpl playBackTo(TImpl obj) {
    try {
      for (Message message : messages) {
        message.method.invoke(obj, message.args);
      }
      return obj;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

Runner

public class VCRRunner {
  interface Hoge {
    public void hoge();
    public void fuga();
  }
  
  static class HogeImpl implements Hoge {
    String s;
    public HogeImpl(String s) {this.s = s;}
    public void hoge() {s += ":hoge";}
    public void fuga() {s += ":fuga";}
  }
  
  public static void main(String[] args) {
    VCR<Hoge> hogeVCR = new VCR<Hoge>(Hoge.class);
    //記録...
    hogeVCR.rec().hoge();
    hogeVCR.rec().fuga();
    
    //再生...
    HogeImpl hoge1 = new HogeImpl("hoge1");
    hogeVCR.playBackTo(hoge1);
    System.out.println(hoge1.s);
    //prints "hoge1:hoge:fuga"
    
    System.out.println(hogeVCR.playBackTo(new HogeImpl("ぬるぽ")).s);
    //prints "ぬるぽ:hoge:fuga"
  }
}

Output

hoge1:hoge:fuga
ぬるぽ:hoge:fuga

考察

型の縛りが厳しいけど、一応似たようなことはできるらしい。
JavaではメソッドシグネチャではなくMethodクラスのオブジェクトがメッセージとしてやりとりされる。で良いのかな。Fieldクラスのオブジェクトもあるか。