java: adds TemplateBuilder and ReflectionTemplateBuilder

This commit is contained in:
FURUHASHI Sadayuki 2010-11-30 21:59:07 +09:00
parent 5f07215662
commit 6eedb50f56
3 changed files with 849 additions and 0 deletions

View File

@ -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<Object> type = (Class<Object>)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<Object> type = (Class<Object>)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);
}
}

View File

@ -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<? extends Annotation> 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<FieldList.Entry> 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<FieldEntry> indexed = new ArrayList<FieldEntry>();
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<Field[]> succ = new ArrayList<Field[]>();
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<? extends Annotation> with) {
return ao.getAnnotation(with) != null;
}
}

View File

@ -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<Integer> f0;
public List<Integer> f1;
public List<String> f2;
public List<List<String>> f3;
public List<SampleListNestedType> f4;
public SampleListTypes() {
}
}
@MessagePackMessage
public static class SampleListNestedType {
public byte[] f0;
public String f1;
public SampleListNestedType() {
}
}
public static class SampleMapTypes {
public Map<Integer, Integer> f0;
public Map<Integer, Integer> f1;
public Map<String, Integer> 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<Integer>();
src.f1 = new ArrayList<Integer>();
src.f1.add(1);
src.f1.add(2);
src.f1.add(3);
src.f2 = new ArrayList<String>();
src.f2.add("e1");
src.f2.add("e2");
src.f2.add("e3");
src.f3 = new ArrayList<List<String>>();
src.f3.add(src.f2);
src.f4 = new ArrayList<SampleListNestedType>();
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<String> srclist = src.f3.get(i);
List<String> 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);
}
}
}