かとじゅんの技術日誌

技術の話をするところ

シリアライズ

id:koichikさんのソースを参考にシリアライズの実装を考えてみました.
SerializebleObjectTestで,シリアライズもデシリアライズもうまくいっています.

しかし,SerializebleObjectTestをextends S2TestCaseBaseしてしまうと,例外が発生してしまうんだよなぁ.
なぜだろう.困った.

java.lang.RuntimeException: java.lang.RuntimeException: javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of  org/seasar/framework/unit/UnitClassLoader): attempted  duplicate class definition for name: "org/seasar/chronos/extension/persitense/TestTaskProxy"
	at org.seasar.chronos.extension.persitense.SerializableObject.readObject(SerializableObject.java:48)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1849)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1753)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
	at org.seasar.chronos.extension.persitense.SerializebleObjectTest.testDeserializebleObject(SerializebleObjectTest.java:62)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at junit.framework.TestCase.runTest(TestCase.java:164)
	at org.seasar.framework.unit.S2FrameworkTestCase.doRunTest(S2FrameworkTestCase.java:519)
	at org.seasar.extension.unit.S2TestCase.doRunTest(S2TestCase.java:103)
	at org.seasar.framework.unit.S2FrameworkTestCase.runBare(S2FrameworkTestCase.java:308)
	at junit.framework.TestResult$1.protect(TestResult.java:110)
	at junit.framework.TestResult.runProtected(TestResult.java:128)
	at junit.framework.TestResult.run(TestResult.java:113)
	at junit.framework.TestCase.run(TestCase.java:120)
	at junit.framework.TestSuite.runTest(TestSuite.java:228)
	at junit.framework.TestSuite.run(TestSuite.java:223)
	at org.junit.internal.runners.OldTestClassRunner.run(OldTestClassRunner.java:35)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: java.lang.RuntimeException: javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of  org/seasar/framework/unit/UnitClassLoader): attempted  duplicate class definition for name: "org/seasar/chronos/extension/persitense/TestTaskProxy"
	at org.seasar.chronos.extension.persitense.SerializableObject.readObject(SerializableObject.java:48)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1849)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1753)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
	at java.util.HashMap.readObject(HashMap.java:1030)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:974)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1849)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1753)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
	at org.seasar.chronos.extension.persitense.SerializableObject.readObject(SerializableObject.java:45)
	... 31 more
Caused by: javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of  org/seasar/framework/unit/UnitClassLoader): attempted  duplicate class definition for name: "org/seasar/chronos/extension/persitense/TestTaskProxy"
	at javassist.ClassPool.toClass(ClassPool.java:904)
	at javassist.ClassPool.toClass(ClassPool.java:847)
	at javassist.ClassPool.toClass(ClassPool.java:805)
	at javassist.CtClass.toClass(CtClass.java:1037)
	at org.seasar.chronos.extension.persitense.SerializeFactory.createProxy(SerializeFactory.java:30)
	at org.seasar.chronos.extension.persitense.SerializableObject.readObject(SerializableObject.java:43)
	... 51 more
Caused by: java.lang.LinkageError: loader (instance of  org/seasar/framework/unit/UnitClassLoader): attempted  duplicate class definition for name: "org/seasar/chronos/extension/persitense/TestTaskProxy"
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:465)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at javassist.ClassPool.toClass2(ClassPool.java:916)
	at javassist.ClassPool.toClass(ClassPool.java:898)
	... 56 more

public class SerializeFactory {

	public static Class<?> createProxy(Class<?> target)
			throws NotFoundException, CannotCompileException {
		String proxyClassName = "org.seasar.chronos.extension.persitense."
				+ target.getSimpleName() + "Proxy";
		try {
			Class<?> clazz = Class.forName(proxyClassName);
			return clazz;
		} catch (ClassNotFoundException e) {
		}

		ClassPool cp = new ClassPool();
		cp.appendClassPath(new LoaderClassPath(ProxyFactory.class
				.getClassLoader()));
		CtClass formCtClass = cp.get(target.getName());
		CtClass formProxyCtClass = cp.makeClass(proxyClassName, formCtClass);
		formProxyCtClass.setSuperclass(formCtClass);
		addWriteReplaceMethod(target.getName(), formProxyCtClass);
		return formProxyCtClass.toClass();
	}

	private static void addWriteReplaceMethod(String className, CtClass target)
			throws CannotCompileException {
		String body = "public Object writeReplace() throws java.io.ObjectStreamException { return org.seasar.chronos.extension.persitense.SerializableObject.create(\""
				+ className + "\", this); }";
		CtMethod method = CtMethod.make(body, target);
		target.addMethod(method);
	}
}
public class SerializableObject implements Serializable {

	private static final long serialVersionUID = 1L;

	private String className;

	private Object target;

	public static SerializableObject create(String className, Object target) {
		return new SerializableObject(className, target);
	}

	public SerializableObject(String className, Object target) {
		this.className = className;
		this.target = target;
	}

	private void writeObject(ObjectOutputStream stream) throws IOException {
		stream.writeObject(className);
		stream.writeObject(createFieldMap());
	}

	@SuppressWarnings("unchecked")
	private void readObject(ObjectInputStream stream) throws IOException,
			ClassNotFoundException {
		try {
			className = String.class.cast(stream.readObject());
			Class sourceClazz = Class.forName(className);
			Class clazz = SerializeFactory.createProxy(sourceClazz);
			target = clazz.newInstance();
			Map<String, Object> fieldMap = Map.class.cast(stream.readObject());
			restoreFields(fieldMap);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private Object readResolve() throws ObjectStreamException {
		return target;
	}

	private Map createFieldMap() {
		try {
			Map<String, Object> fieldMap = new HashMap<String, Object>();
			Class<? extends Object> clazz = target.getClass();
			while (clazz != Object.class) {
				Field[] fields = clazz.getDeclaredFields();
				for (int i = 0; i < fields.length; i++) {
					if (!fields[i].isAccessible()) {
						fields[i].setAccessible(true);
					}
					Object value = fields[i].get(target);
					if (value != null) {
						fieldMap.put(fields[i].getName(), value);
					}
				}
				clazz = clazz.getSuperclass();
			}
			return fieldMap;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private void restoreFields(Map<String, Object> fieldMap) {
		try {
			Class clazz = target.getClass();
			while (clazz != Object.class) {
				Field[] fields = clazz.getDeclaredFields();
				for (int i = 0; i < fields.length; i++) {
					int modifiers = fields[i].getModifiers();
					if (Modifier.isFinal(modifiers)) {
						continue;
					}
					if (!fields[i].isAccessible()) {
						fields[i].setAccessible(true);
					}
					Object value = fieldMap.get(fields[i].getName());
					if (value instanceof SerializableObject) { // ポイント!!
						value = SerializableObject.class.cast(value)
								.readResolve();
					}
					fields[i].set(target, value);
				}
				clazz = clazz.getSuperclass();
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public String toString() {
		return new ToStringBuilder(this).toString();
	}

	public Object getTarget() {
		return target;
	}

}
public class TestTask implements Serializable {

	private static final long serialVersionUID = 1L;

	public String name = "aaaa";
	public Integer id = 10;

	public TestTask testTask = this;

	@Override
	public String toString() {
		return new ToStringBuilder(this).toString();
	}
}
public class SerializebleObjectTest {

	private static final long serialVersionUID = 1L;

	@Test
	public void testSerializebleObject() throws NotFoundException,
			CannotCompileException, InstantiationException,
			IllegalAccessException {

		Class clazz = SerializeFactory.createProxy(TestTask.class);
		TestTask a = (TestTask) clazz.newInstance();

		a.testTask = (TestTask) clazz.newInstance();
		a.testTask.name = "hoge";

		FileOutputStream fos = null;
		try {
			File targetFile = new File("C:\\temp\\", TestTask.class.getName());
			fos = new FileOutputStream(targetFile);
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(a);
		} catch (IOException e) {
			throw new IORuntimeException(e);
		} finally {
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					throw new IORuntimeException(e);
				}
			}
		}
	}

	@Test
	public void testDeserializebleObject() {
		TestTask target = null;
		String className = TestTask.class.getName();
		FileInputStream fis = null;
		try {
			File targetFile = new File("C:\\temp\\", className);
			if (targetFile.exists()) {
				fis = new FileInputStream(targetFile);
				ObjectInputStream ois = new ObjectInputStream(fis);
				target = (TestTask) ois.readObject();
			}
		} catch (IOException e) {
			throw new IORuntimeException(e);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		} finally {
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					throw new IORuntimeException(e);
				}
			}
		}
		System.out.println(target);
		System.out.println(target.name);
		System.out.println(target.id);
		System.out.println(target.testTask);
		System.out.println(target.testTask.name);
		System.out.println(target.testTask.id);
	}
}
public class S2TestCaseBase extends S2TestCase {

	private static final String PATH = "test.dicon";

	protected void setUp() throws Exception {
		super.setUp();
		include(PATH);
	}
}