Proxy を使ってオブジェクトをコピー可能にする

昨日のネタに関して少し思いついたことを。
配列には使えないけど、インターフェースを宣言している場合には java.lang.reflect.Proxy を使えば Dynamic Proxy を生成できる。そうすると動的に copy メソッドを追加される。

public interface Copyable<E> {
    E copy();
}

としておいて。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class CopyUtils {
    @SuppressWarnings("unchecked")
    public static <E> E toCopyable(E obj) {
        if (obj instanceof Copyable) {
            return obj;
        }
        
        InvocationHandler handler = new CopyProxy(obj);
        ClassLoader loader = CopyUtils.class.getClassLoader();
        Class[] origInterfaces = obj.getClass().getInterfaces();
        Class[] interfaces = new Class[origInterfaces.length  + 1];
        interfaces[0] = Copyable.class;
        System.arraycopy(origInterfaces, 0, interfaces, 1, origInterfaces.length);
        
        return (E) Proxy.newProxyInstance(loader, interfaces, handler);
    }
    
    private static class CopyProxy implements InvocationHandler {
        private static final Method clone;
        
        private static final Method copy;
        
        private Object obj;
        
        static {
            try {
                clone = Object.class.getDeclaredMethod("clone");
                copy = Copyable.class.getDeclaredMethod("copy");
            } catch (NoSuchMethodException e) {
                throw new InternalError();
            }
        }
        
        public CopyProxy(Object obj) {
            this.obj = obj;
        }
        
        public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
            if (m.equals(copy)) {
                return toCopyable(clone.invoke(obj));
            }
            
            return m.invoke(obj, args);
        }
    }

}

使用例

public interface Person {
    public String getName();

    public void setName(String name);
}
public class PersonImpl implements Cloneable {
    private String name;

    public PersonImpl(String name) {
        this.name = name;
    }

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    @Override
    public String toString() { return "Person: " + name; }

    @Override
    public Object clone() { return new PersonImpl(name); }
}
public class CopyTest {
    public static void main(String[] args) {
        Person a = new PersonImp("Bill Gates");
        Person b = CopyUtils.toCopyable(a);
        Person c = ((Copyable<Person>) c).copy();
    }
}