
package com.esotericsoftware.kryo;

import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.serializers.MapSerializer;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class ReferenceTest extends KryoTestCase {
	static public class Ordering {
		public String order;
	}

	static public class Stuff extends TreeMap {
		public Ordering ordering;

		public Stuff (Ordering ordering) {
			this.ordering = ordering;
		}
	}

	public void testChildObjectBeforeReference () {
		Ordering ordering = new Ordering();
		ordering.order = "assbackwards";
		Stuff stuff = new Stuff(ordering);
		stuff.put("key", "value");
		stuff.put("something", 456);
		stuff.put("self", stuff);

		Kryo kryo = new Kryo();
		kryo.addDefaultSerializer(Stuff.class, new MapSerializer() {
			public void write (Kryo kryo, Output output, Map object) {
				kryo.writeObjectOrNull(output, ((Stuff)object).ordering, Ordering.class);
				super.write(kryo, output, object);
			}

			protected Map create (Kryo kryo, Input input, Class<Map> type) {
				Ordering ordering = kryo.readObjectOrNull(input, Ordering.class);
				return new Stuff(ordering);
			}
		});

		Output output = new Output(512, -1);
		kryo.writeObject(output, stuff);

		Input input = new Input(output.getBuffer(), 0, output.position());
		Stuff stuff2 = kryo.readObject(input, Stuff.class);

		assertEquals(stuff.ordering.order, stuff2.ordering.order);
		assertEquals(stuff.get("key"), stuff2.get("key"));
		assertEquals(stuff.get("something"), stuff2.get("something"));
		assertTrue(stuff.get("self") == stuff);
		assertTrue(stuff2.get("self") == stuff2);
	}

	public void testReadingNestedObjectsFirst () {
		ArrayList list = new ArrayList();
		list.add("1");
		list.add("1");
		list.add("2");
		list.add("1");
		list.add("1");
		List subList = list.subList(0, 5);

		kryo.setRegistrationRequired(false);
		kryo.register(ArrayList.class);
		Class<List> subListClass = (Class<List>)subList.getClass();
		if(subListClass.getName().equals("java.util.SubList"))
			kryo.register(subList.getClass(), new SubListSerializer());
		else if(subListClass.getName().equals("java.util.ArrayList$SubList")) {
			// This is JDK > = 1.7
			kryo.register(subList.getClass(), new ArraySubListSerializer());			
		}
		roundTrip(26, subList);
	}

	static public class SubListSerializer extends Serializer<List> {
		private Field listField, offsetField, sizeField;

		public SubListSerializer () {
			try {
				Class sublistClass = Class.forName("java.util.SubList");
				listField = sublistClass.getDeclaredField("l");
				offsetField = sublistClass.getDeclaredField("offset");
				sizeField = sublistClass.getDeclaredField("size");
				listField.setAccessible(true);
				offsetField.setAccessible(true);
				sizeField.setAccessible(true);
			} catch (Exception ex) {
				throw new RuntimeException(ex);
			}
		}

		public void write (Kryo kryo, Output output, List list) {
			try {
				kryo.writeClassAndObject(output, listField.get(list));
				int fromIndex = offsetField.getInt(list);
				int count = sizeField.getInt(list);
				output.writeInt(fromIndex);
				output.writeInt(count);
			} catch (Exception ex) {
				throw new RuntimeException(ex);
			}
		}

		public List read (Kryo kryo, Input input, Class<List> type) {
			List list = (List)kryo.readClassAndObject(input);
			int fromIndex = input.readInt();
			int count = input.readInt();
			return list.subList(fromIndex, fromIndex + count);
		}
	}

	static public class ArraySubListSerializer extends Serializer<List> {
		private Field parentField, offsetField, sizeField;

		public ArraySubListSerializer () {
			try {
				Class sublistClass = Class.forName("java.util.ArrayList$SubList");
				parentField = sublistClass.getDeclaredField("parent");
				offsetField = sublistClass.getDeclaredField("offset");
				sizeField = sublistClass.getDeclaredField("size");
				parentField.setAccessible(true);
				offsetField.setAccessible(true);
				sizeField.setAccessible(true);
			} catch (Exception ex) {
				throw new RuntimeException(ex);
			}
		}

		public void write (Kryo kryo, Output output, List list) {
			try {
				kryo.writeClassAndObject(output, parentField.get(list));
				int offset = offsetField.getInt(list);
				int size = sizeField.getInt(list);
				output.writeInt(offset);
				output.writeInt(size);
			} catch (Exception ex) {
				throw new RuntimeException(ex);
			}
		}

		public List read (Kryo kryo, Input input, Class<List> type) {
			List list = (List)kryo.readClassAndObject(input);
			int offset = input.readInt();
			int size = input.readInt();
			return list.subList(offset, offset + size);
		}
	}
}

@@ -2159,135 +2159,8 @@
 %0D%0A%09%09
-if(subListClass.getName().equals(%22java.util.SubList%22))%0D%0A%09%09%09kryo.register(subList.getClass(), new SubListSerializer());%0D%0A%09%09else 
 if(s
@@ -2322,16 +2322,98 @@
 ));%09%09%09%0D%0A
+%09%09%7D else %7B%0D%0A%09%09%09kryo.register(subList.getClass(), new SubListSerializer());%0D%09%09    %0A
 %09%09%7D%0D%0A%09%09r

Fix ReferenceTest so that it still works under Java6