diff --git a/java/src/main/java/org/msgpack/template/ReflectionTemplateBuilder.java b/java/src/main/java/org/msgpack/template/ReflectionTemplateBuilder.java new file mode 100644 index 00000000..05b8b80a --- /dev/null +++ b/java/src/main/java/org/msgpack/template/ReflectionTemplateBuilder.java @@ -0,0 +1,368 @@ +// +// MessagePack for Java +// +// Copyright (C) 2009-2010 FURUHASHI Sadayuki +// +// 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 org.msgpack.template; + +import java.io.IOException; +import java.lang.reflect.*; +import org.msgpack.*; + +public class ReflectionTemplateBuilder extends TemplateBuilder { + public ReflectionTemplateBuilder() { + } + + public static abstract class ReflectionFieldEntry extends FieldEntry { + public ReflectionFieldEntry(FieldEntry e) { + super(e.getField(), e.getOption()); + } + + public abstract void pack(Object target, Packer pac) throws IOException; + + public abstract void convert(Object target, MessagePackObject obj) throws MessageTypeException, IllegalAccessException; + + public abstract void unpack(Object target, Unpacker pac) throws IOException, MessageTypeException, IllegalAccessException; + + public void setNull(Object target) throws IllegalAccessException { + getField().set(target, null); + } + } + + public static class ObjectFieldEntry extends ReflectionFieldEntry { + public ObjectFieldEntry(FieldEntry e) { + super(e); + } + + public void pack(Object target, Packer pac) throws IOException { + // TODO: call template + pac.pack(target); + } + + public void convert(Object target, MessagePackObject obj) throws MessageTypeException, IllegalAccessException { + // TODO: call template + Field f = getField(); + Class type = (Class)f.getType(); + Object fieldReference = f.get(target); + Object valueReference = obj.convert(type, fieldReference); + if(valueReference != fieldReference) { + f.set(target, valueReference); + } + } + + public void unpack(Object target, Unpacker pac) throws IOException, MessageTypeException, IllegalAccessException { + // TODO: call template + Field f = getField(); + Class type = (Class)f.getType(); + Object fieldReference = f.get(target); + Object valueReference = pac.unpack(type, fieldReference); + if(valueReference != fieldReference) { + f.set(target, valueReference); + } + } + } + + public static class BooleanFieldEntry extends ReflectionFieldEntry { + public BooleanFieldEntry(FieldEntry e) { + super(e); + } + public void pack(Object target, Packer pac) throws IOException { + pac.pack((boolean)(Boolean)target); + } + public void convert(Object target, MessagePackObject obj) throws MessageTypeException, IllegalAccessException { + getField().setBoolean(target, obj.asBoolean()); + } + public void unpack(Object target, Unpacker pac) throws IOException, MessageTypeException, IllegalAccessException { + getField().setBoolean(target, pac.unpackBoolean()); + } + } + + public static class ByteFieldEntry extends ReflectionFieldEntry { + public ByteFieldEntry(FieldEntry e) { + super(e); + } + public void pack(Object target, Packer pac) throws IOException { + pac.pack((byte)(Byte)target); + } + public void convert(Object target, MessagePackObject obj) throws MessageTypeException, IllegalAccessException { + getField().setByte(target, obj.asByte()); + } + public void unpack(Object target, Unpacker pac) throws IOException, MessageTypeException, IllegalAccessException { + getField().setByte(target, pac.unpackByte()); + } + } + + public static class ShortFieldEntry extends ReflectionFieldEntry { + public ShortFieldEntry(FieldEntry e) { + super(e); + } + public void pack(Object target, Packer pac) throws IOException { + pac.pack((short)(Short)target); + } + public void convert(Object target, MessagePackObject obj) throws MessageTypeException, IllegalAccessException { + getField().setShort(target, obj.asShort()); + } + public void unpack(Object target, Unpacker pac) throws IOException, MessageTypeException, IllegalAccessException { + getField().setShort(target, pac.unpackShort()); + } + } + + public static class IntFieldEntry extends ReflectionFieldEntry { + public IntFieldEntry(FieldEntry e) { + super(e); + } + public void pack(Object target, Packer pac) throws IOException { + pac.pack((int)(Integer)target); + } + public void convert(Object target, MessagePackObject obj) throws MessageTypeException, IllegalAccessException { + getField().setInt(target, obj.asInt()); + } + public void unpack(Object target, Unpacker pac) throws IOException, MessageTypeException, IllegalAccessException { + getField().setInt(target, pac.unpackInt()); + } + } + + public static class LongFieldEntry extends ReflectionFieldEntry { + public LongFieldEntry(FieldEntry e) { + super(e); + } + public void pack(Object target, Packer pac) throws IOException { + pac.pack((long)(Long)target); + } + public void convert(Object target, MessagePackObject obj) throws MessageTypeException, IllegalAccessException { + getField().setLong(target, obj.asLong()); + } + public void unpack(Object target, Unpacker pac) throws IOException, MessageTypeException, IllegalAccessException { + getField().setLong(target, pac.unpackLong()); + } + } + + public static class FloatFieldEntry extends ReflectionFieldEntry { + public FloatFieldEntry(FieldEntry e) { + super(e); + } + public void pack(Object target, Packer pac) throws IOException { + pac.pack((float)(Float)target); + } + public void convert(Object target, MessagePackObject obj) throws MessageTypeException, IllegalAccessException { + getField().setFloat(target, obj.asFloat()); + } + public void unpack(Object target, Unpacker pac) throws IOException, MessageTypeException, IllegalAccessException { + getField().setFloat(target, pac.unpackFloat()); + } + } + + public static class DoubleFieldEntry extends ReflectionFieldEntry { + public DoubleFieldEntry(FieldEntry e) { + super(e); + } + public void pack(Object target, Packer pac) throws IOException { + pac.pack((double)(Double)target); + } + public void convert(Object target, MessagePackObject obj) throws MessageTypeException, IllegalAccessException { + getField().setDouble(target, obj.asDouble()); + } + public void unpack(Object target, Unpacker pac) throws IOException, MessageTypeException, IllegalAccessException { + getField().setDouble(target, pac.unpackDouble()); + } + } + + private static class ReflectionTemplate extends AbstractTemplate { + private Class targetClass; + private ReflectionFieldEntry[] entries; + private int minimumArrayLength; + + public ReflectionTemplate(Class targetClass, ReflectionFieldEntry[] entries) { + this.targetClass = targetClass; + this.entries = entries; + for(int i=0; i < entries.length; i++) { + if(entries[i].isRequired() || entries[i].isNullable()) { + minimumArrayLength = i+1; + } + } + } + + public void pack(Packer pk, Object target) throws IOException { + try { + pk.packArray(entries.length); + for(ReflectionFieldEntry e : entries) { + if(!e.isAvailable()) { + pk.packNil(); + continue; + } + Object obj = e.getField().get(target); + if(obj == null) { + if(!e.isNullable() && !e.isOptional()) { + throw new MessageTypeException(); + } + pk.packNil(); + } else { + pk.pack(obj); // TODO: call template + } + } + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new MessageTypeException(e); + } + } + + public Object unpack(Unpacker pac, Object to) throws IOException, MessageTypeException { + try { + if(to == null) { + to = targetClass.newInstance(); + } + + int length = pac.unpackArray(); + if(length < minimumArrayLength) { + throw new MessageTypeException(); + } + + int i; + for(i=0; i < minimumArrayLength; i++) { + ReflectionFieldEntry e = entries[i]; + if(pac.tryUnpackNull()) { + if(e.isRequired()) { + // Requred + nil => exception + throw new MessageTypeException(); + } else if(e.isOptional()) { + // Optional + nil => keep default value + continue; + } else { // Nullable + // Nullable + nil => set null + e.setNull(to); + continue; + } + } + e.unpack(to, pac); + } + + int max = length < entries.length ? length : entries.length; + for(; i < max; i++) { + ReflectionFieldEntry e = entries[i]; + if(pac.tryUnpackNull()) { + // this is Optional field becaue i >= minimumArrayLength + // Optional + nil => keep default value + continue; + } + e.unpack(to, pac); + } + + // latter entries are all Optional + nil => keep default value + + for(; i < length; i++) { + pac.unpackObject(); + } + + return to; + + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new MessageTypeException(e); + } + } + + public Object convert(MessagePackObject from, Object to) throws MessageTypeException { + try { + if(to == null) { + to = targetClass.newInstance(); + } + + MessagePackObject[] array = from.asArray(); + int length = array.length; + if(length < minimumArrayLength) { + throw new MessageTypeException(); + } + + int i; + for(i=0; i < minimumArrayLength; i++) { + ReflectionFieldEntry e = entries[i]; + MessagePackObject obj = array[i]; + if(obj.isNil()) { + if(e.isRequired()) { + // Requred + nil => exception + throw new MessageTypeException(); + } else if(e.isOptional()) { + // Optional + nil => keep default value + continue; + } else { // Nullable + // Nullable + nil => set null + e.setNull(to); + continue; + } + } + e.convert(to, obj); + } + + int max = length < entries.length ? length : entries.length; + for(; i < max; i++) { + ReflectionFieldEntry e = entries[i]; + MessagePackObject obj = array[i]; + if(obj.isNil()) { + // this is Optional field becaue i >= minimumArrayLength + // Optional + nil => keep default value + continue; + } + e.convert(to, obj); + } + + // latter entries are all Optional + nil => keep default value + + return to; + + } catch (Exception e) { + throw new MessageTypeException(e); + } + } + } + + public Template buildTemplate(Class targetClass, FieldEntry[] entries) { + for(FieldEntry e : entries) { + Field f = e.getField(); + int mod = f.getModifiers(); + if(!Modifier.isPublic(mod)) { + f.setAccessible(true); + } + } + + ReflectionFieldEntry[] res = new ReflectionFieldEntry[entries.length]; + for(int i=0; i < entries.length; i++) { + FieldEntry e = entries[i]; + Class type = e.getType(); + System.out.println(type); + if(type.equals(boolean.class)) { + res[i] = new BooleanFieldEntry(e); + } else if(type.equals(byte.class)) { + res[i] = new ByteFieldEntry(e); + } else if(type.equals(short.class)) { + res[i] = new ShortFieldEntry(e); + } else if(type.equals(int.class)) { + res[i] = new IntFieldEntry(e); + } else if(type.equals(long.class)) { + res[i] = new LongFieldEntry(e); + } else if(type.equals(float.class)) { + res[i] = new FloatFieldEntry(e); + } else if(type.equals(double.class)) { + res[i] = new DoubleFieldEntry(e); + } else { + res[i] = new ObjectFieldEntry(e); + } + } + + return new ReflectionTemplate(targetClass, res); + } +} + diff --git a/java/src/main/java/org/msgpack/template/TemplateBuilder.java b/java/src/main/java/org/msgpack/template/TemplateBuilder.java new file mode 100644 index 00000000..93898650 --- /dev/null +++ b/java/src/main/java/org/msgpack/template/TemplateBuilder.java @@ -0,0 +1,263 @@ +// +// MessagePack for Java +// +// Copyright (C) 2009-2010 FURUHASHI Sadayuki +// +// 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 org.msgpack.template; + +import java.io.IOException; +import java.lang.reflect.*; +import java.lang.annotation.*; +import java.util.List; +import java.util.ArrayList; +import org.msgpack.*; +import org.msgpack.annotation.*; + +public abstract class TemplateBuilder { + public static class FieldEntry { + private Field field = null; + private FieldOption option = FieldOption.IGNORE; + + public FieldEntry() { + } + + public FieldEntry(Field field, FieldOption option) { + this.field = field; + this.option = option; + } + + public Field getField() { + return field; + } + + public String getName() { + return field.getName(); + } + + public Class getType() { + return field.getType(); + } + + public FieldOption getOption() { + return option; + } + + public boolean isAvailable() { + return option != FieldOption.IGNORE; + } + + public boolean isRequired() { + return option == FieldOption.REQUIRED; + } + + public boolean isOptional() { + return option == FieldOption.OPTIONAL; + } + + public boolean isNullable() { + return option == FieldOption.NULLABLE; + } + + public boolean isAnnotated(Class with) { + return field.getAnnotation(with) != null; + } + } + + // Override this method + public abstract Template buildTemplate(Class targetClass, FieldEntry[] entries); + + public Template buildTemplate(Class targetClass) { + return buildTemplate(targetClass, readFieldEntries(targetClass)); + } + + public Template buildTemplate(Class targetClass, FieldOption implicitOption) { + return buildTemplate(targetClass, readFieldEntries(targetClass, implicitOption)); + } + + public Template buildTemplate(Class targetClass, FieldList flist) throws NoSuchFieldException { + return buildTemplate(targetClass, convertFieldEntries(targetClass, flist)); + } + + + private static TemplateBuilder instance; + + static { + // FIXME + instance = new ReflectionTemplateBuilder(); + } + + + public static Template build(Class targetClass) { + return instance.buildTemplate(targetClass); + } + + public static Template build(Class targetClass, FieldOption implicitOption) { + return instance.buildTemplate(targetClass, implicitOption); + } + + public static Template build(Class targetClass, FieldList flist) throws NoSuchFieldException { + return instance.buildTemplate(targetClass, flist); + } + + + protected FieldEntry[] convertFieldEntries(Class targetClass, FieldList flist) throws NoSuchFieldException { + List src = flist.getList(); + FieldEntry[] result = new FieldEntry[src.size()]; + for(int i=0; i < src.size(); i++) { + FieldList.Entry s = src.get(i); + if(s.isAvailable()) { + result[i] = new FieldEntry(targetClass.getDeclaredField(s.getName()), s.getOption()); + } else { + result[i] = new FieldEntry(); + } + } + return result; + } + + protected FieldEntry[] readFieldEntries(Class targetClass) { + FieldOption implicitOption = readImplicitFieldOption(targetClass); + return readFieldEntries(targetClass, implicitOption); + } + + protected FieldEntry[] readFieldEntries(Class targetClass, FieldOption implicitOption) { + Field[] allFields = readAllFields(targetClass); + + /* index: + * @Index(0) int a; // 0 + * int b; // 1 + * @Index(3) int c; // 3 + * int e; // 4 + * @Index(2) int d; // 2 + * int e; // 5 + */ + List indexed = new ArrayList(); + int maxIndex = -1; + for(Field f : allFields) { + FieldOption opt = readFieldOption(f, implicitOption); + if(opt == FieldOption.IGNORE) { + // skip + continue; + } + + int index = readFieldIndex(f, maxIndex); + + if(indexed.size() > index && indexed.get(index) != null) { + throw new RuntimeException("duplicated index: "+index); // FIXME exception + } + if(index < 0) { + throw new RuntimeException("invalid index: "+index); // FIXME exception + } + + while(indexed.size() <= index) { + indexed.add(null); + } + indexed.set(index, new FieldEntry(f, opt)); + + if(maxIndex < index) { + maxIndex = index; + } + } + + FieldEntry[] result = new FieldEntry[maxIndex+1]; + for(int i=0; i < indexed.size(); i++) { + FieldEntry e = indexed.get(i); + if(e == null) { + result[i] = new FieldEntry(); + } else { + result[i] = new FieldEntry(e.getField(), e.getOption()); + } + } + + return result; + } + + private Field[] readAllFields(Class targetClass) { + // order: [fields of super class, ..., fields of this class] + List succ = new ArrayList(); + int total = 0; + for(Class c = targetClass; c != Object.class; c = c.getSuperclass()) { + Field[] fields = c.getDeclaredFields(); + total += fields.length; + succ.add(fields); + } + Field[] result = new Field[total]; + int off = 0; + for(int i=succ.size()-1; i >= 0; i--) { + Field[] fields = succ.get(0); + System.arraycopy(fields, 0, result, off, fields.length); + off += fields.length; + } + return result; + } + + private FieldOption readImplicitFieldOption(Class targetClass) { + MessagePackMessage a = targetClass.getAnnotation(MessagePackMessage.class); + if(a == null) { + return FieldOption.DEFAULT; + } + return a.value(); + } + + private FieldOption readFieldOption(Field field, FieldOption implicitOption) { + int mod = field.getModifiers(); + if(Modifier.isStatic(mod) || Modifier.isFinal(mod)) { + return FieldOption.IGNORE; + } + + if(isAnnotated(field, Ignore.class)) { + return FieldOption.IGNORE; + } else if(isAnnotated(field, Required.class)) { + return FieldOption.REQUIRED; + } else if(isAnnotated(field, Optional.class)) { + return FieldOption.OPTIONAL; + } else if(isAnnotated(field, Nullable.class)) { + if(field.getDeclaringClass().isPrimitive()) { + return FieldOption.REQUIRED; + } else { + return FieldOption.NULLABLE; + } + } + + if(implicitOption != FieldOption.DEFAULT) { + return implicitOption; + } + + // default mode: + // transient : Ignore + // public : Nullable + // others : Ignore + if(Modifier.isTransient(mod)) { + return FieldOption.IGNORE; + } else if(Modifier.isPublic(mod)) { + return FieldOption.NULLABLE; + } else { + return FieldOption.IGNORE; + } + } + + private int readFieldIndex(Field field, int maxIndex) { + Index a = field.getAnnotation(Index.class); + if(a == null) { + return maxIndex + 1; + } else { + return a.value(); + } + } + + protected boolean isAnnotated(AccessibleObject ao, Class with) { + return ao.getAnnotation(with) != null; + } +} + diff --git a/java/src/test/java/org/msgpack/template/TestTemplateBuilder.java b/java/src/test/java/org/msgpack/template/TestTemplateBuilder.java new file mode 100644 index 00000000..70e9bda5 --- /dev/null +++ b/java/src/test/java/org/msgpack/template/TestTemplateBuilder.java @@ -0,0 +1,218 @@ +package org.msgpack.template; + +import java.util.*; +import java.io.*; +import java.math.*; +import org.msgpack.*; +import org.msgpack.annotation.*; + +import org.junit.Test; +import junit.framework.TestCase; + +public class TestTemplateBuilder extends TestCase { + public static class PrimitiveTypeFieldsClass { + public byte f0; + public short f1; + public int f2; + public long f3; + public float f4; + public double f5; + public boolean f6; + + public PrimitiveTypeFieldsClass() { + } + } + + public static class GeneralReferenceTypeFieldsClass { + public Byte f0; + public Short f1; + public Integer f2; + public Long f3; + public Float f4; + public Double f5; + public Boolean f6; + public BigInteger f7; + public String f8; + public byte[] f9; + + public GeneralReferenceTypeFieldsClass() { + } + } + + public static class SampleListTypes { + public List f0; + public List f1; + public List f2; + public List> f3; + public List f4; + + public SampleListTypes() { + } + } + + @MessagePackMessage + public static class SampleListNestedType { + public byte[] f0; + public String f1; + + public SampleListNestedType() { + } + } + + public static class SampleMapTypes { + public Map f0; + public Map f1; + public Map f2; + + public SampleMapTypes() { + } + } + + static void buildAndRegisterTemplate(Class targetClass) { + MessagePack.register(targetClass, + TemplateBuilder.build(targetClass)); + } + + static { + buildAndRegisterTemplate(PrimitiveTypeFieldsClass.class); + buildAndRegisterTemplate(GeneralReferenceTypeFieldsClass.class); + buildAndRegisterTemplate(SampleListNestedType.class); + buildAndRegisterTemplate(SampleListTypes.class); + buildAndRegisterTemplate(SampleMapTypes.class); + } + + @Test + public void testPrimitiveTypeFieldsClass00() throws Exception { + PrimitiveTypeFieldsClass src = new PrimitiveTypeFieldsClass(); + src.f0 = (byte) 0; + src.f1 = 1; + src.f2 = 2; + src.f3 = 3; + src.f4 = 4; + src.f5 = 5; + src.f6 = false; + + byte[] raw = MessagePack.pack(src); + + PrimitiveTypeFieldsClass dstu = + MessagePack.unpack(raw, PrimitiveTypeFieldsClass.class); + assertEquals(src.f0, dstu.f0); + assertEquals(src.f1, dstu.f1); + assertEquals(src.f2, dstu.f2); + assertEquals(src.f3, dstu.f3); + assertEquals(src.f4, dstu.f4); + assertEquals(src.f5, dstu.f5); + assertEquals(src.f6, dstu.f6); + + MessagePackObject o = MessagePack.unpack(raw); + PrimitiveTypeFieldsClass dsto = + o.convert(PrimitiveTypeFieldsClass.class); + + assertEquals(src.f0, dsto.f0); + assertEquals(src.f1, dsto.f1); + assertEquals(src.f2, dsto.f2); + assertEquals(src.f3, dsto.f3); + assertEquals(src.f4, dsto.f4); + assertEquals(src.f5, dsto.f5); + assertEquals(src.f6, dsto.f6); + } + + public void testGeneralReferenceTypeFieldsClass() throws Exception { + GeneralReferenceTypeFieldsClass src = new GeneralReferenceTypeFieldsClass(); + src.f0 = 0; + src.f1 = 1; + src.f2 = 2; + src.f3 = (long) 3; + src.f4 = (float) 4; + src.f5 = (double) 5; + src.f6 = false; + src.f7 = new BigInteger("7"); + src.f8 = "8"; + src.f9 = new byte[] { 0x01, 0x02 }; + + byte[] raw = MessagePack.pack(src); + + GeneralReferenceTypeFieldsClass dstu = + MessagePack.unpack(raw, GeneralReferenceTypeFieldsClass.class); + assertEquals(src.f0, dstu.f0); + assertEquals(src.f1, dstu.f1); + assertEquals(src.f2, dstu.f2); + assertEquals(src.f3, dstu.f3); + assertEquals(src.f4, dstu.f4); + assertEquals(src.f5, dstu.f5); + assertEquals(src.f6, dstu.f6); + assertEquals(src.f7, dstu.f7); + assertEquals(src.f8, dstu.f8); + assertEquals(src.f9[0], dstu.f9[0]); + assertEquals(src.f9[1], dstu.f9[1]); + + MessagePackObject o = MessagePack.unpack(raw); + GeneralReferenceTypeFieldsClass dsto = + o.convert(GeneralReferenceTypeFieldsClass.class); + assertEquals(src.f0, dsto.f0); + assertEquals(src.f1, dsto.f1); + assertEquals(src.f2, dsto.f2); + assertEquals(src.f3, dsto.f3); + assertEquals(src.f4, dsto.f4); + assertEquals(src.f5, dsto.f5); + assertEquals(src.f6, dsto.f6); + assertEquals(src.f7, dsto.f7); + assertEquals(src.f8, dsto.f8); + assertEquals(src.f9[0], dsto.f9[0]); + assertEquals(src.f9[1], dsto.f9[1]); + } + + @Test + public void testListTypes() throws Exception { + SampleListTypes src = new SampleListTypes(); + src.f0 = new ArrayList(); + src.f1 = new ArrayList(); + src.f1.add(1); + src.f1.add(2); + src.f1.add(3); + src.f2 = new ArrayList(); + src.f2.add("e1"); + src.f2.add("e2"); + src.f2.add("e3"); + src.f3 = new ArrayList>(); + src.f3.add(src.f2); + src.f4 = new ArrayList(); + SampleListNestedType slnt = new SampleListNestedType(); + slnt.f0 = new byte[] { 0x01, 0x02 }; + slnt.f1 = "muga"; + src.f4.add(slnt); + + byte[] raw = MessagePack.pack(src); + + SampleListTypes dstu = + MessagePack.unpack(raw, SampleListTypes.class); + assertEquals(src.f0.size(), dstu.f0.size()); + assertEquals(src.f1.size(), dstu.f1.size()); + for (int i = 0; i < src.f1.size(); ++i) { + assertEquals(src.f1.get(i), dstu.f1.get(i)); + } + assertEquals(src.f2.size(), dstu.f2.size()); + for (int i = 0; i < src.f2.size(); ++i) { + assertEquals(src.f2.get(i), dstu.f2.get(i)); + } + assertEquals(src.f3.size(), dstu.f3.size()); + for (int i = 0; i < src.f3.size(); ++i) { + List srclist = src.f3.get(i); + List dstlist = dstu.f3.get(i); + assertEquals(srclist.size(), dstlist.size()); + for (int j = 0; j < srclist.size(); ++j) { + assertEquals(srclist.get(j), dstlist.get(j)); + } + } + assertEquals(src.f4.size(), dstu.f4.size()); + for (int i = 0; i < src.f4.size(); ++i) { + SampleListNestedType s = src.f4.get(i); + SampleListNestedType d = dstu.f4.get(i); + assertEquals(s.f0[0], d.f0[0]); + assertEquals(s.f0[1], d.f0[1]); + assertEquals(s.f1, d.f1); + } + } + +} +