diff --git a/java/src/main/java/org/msgpack/annotation/MessagePackNullable.java b/java/src/main/java/org/msgpack/annotation/MessagePackNullable.java new file mode 100644 index 00000000..a3ec9a55 --- /dev/null +++ b/java/src/main/java/org/msgpack/annotation/MessagePackNullable.java @@ -0,0 +1,28 @@ +// +// 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface MessagePackNullable { +} diff --git a/java/src/main/java/org/msgpack/util/codegen/DynamicCodeGen.java b/java/src/main/java/org/msgpack/util/codegen/DynamicCodeGen.java index 50cedd5a..7bee45d2 100644 --- a/java/src/main/java/org/msgpack/util/codegen/DynamicCodeGen.java +++ b/java/src/main/java/org/msgpack/util/codegen/DynamicCodeGen.java @@ -39,7 +39,9 @@ import org.msgpack.Packer; import org.msgpack.Template; import org.msgpack.Unpacker; import org.msgpack.annotation.MessagePackOptional; +import org.msgpack.annotation.MessagePackNullable; import org.msgpack.template.OptionalTemplate; +import org.msgpack.template.NullableTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -253,7 +255,6 @@ class DynamicCodeGen extends DynamicCodeGenBase implements Constants { } Template createTemplate(Field field) { - boolean isOptional = isAnnotated(field, MessagePackOptional.class); Class c = field.getType(); Template tmpl = null; if (List.class.isAssignableFrom(c) || Map.class.isAssignableFrom(c)) { @@ -261,12 +262,15 @@ class DynamicCodeGen extends DynamicCodeGenBase implements Constants { } else { tmpl = createTemplate(c); } - if (isOptional) { - // for pack + if (isAnnotated(field, MessagePackOptional.class)) { + // @Optional types return new OptionalTemplate(tmpl); - } else { - return tmpl; } + if (!c.isPrimitive() && isAnnotated(field, MessagePackNullable.class)) { + // @Nullable reference types + return new NullableTemplate(tmpl); + } + return tmpl; } private boolean isAnnotated(Field field, Class with) { diff --git a/java/src/test/java/org/msgpack/TestAnnotations.java b/java/src/test/java/org/msgpack/TestAnnotations.java new file mode 100644 index 00000000..d6eb3d8f --- /dev/null +++ b/java/src/test/java/org/msgpack/TestAnnotations.java @@ -0,0 +1,150 @@ +package org.msgpack; + +import org.msgpack.*; +import org.msgpack.object.*; +import org.msgpack.annotation.*; +import static org.msgpack.Templates.*; + +import java.io.*; +import java.util.*; +import java.math.BigInteger; + +import org.junit.Test; +import junit.framework.TestCase; + +public class TestAnnotations extends TestCase { + + @MessagePackMessage + public static class MyClassVersion1 { + // required field, not nullable. + public String name; + + // required and nullable field. + @MessagePackNullable + public String nickname; + } + + + @MessagePackMessage + public static class MyClassVersion2 { + public String name; + + @MessagePackNullable + public String nickname; + + // adds an optional field on version 2. + @MessagePackOptional + public int age = -1; + } + + + @MessagePackMessage + public static class MyClassVersion3 { + public String name; + + @MessagePackNullable + public String nickname; + + // adds required fields on version 3, then + // this class is NOT compatible with version 1. + public int age; + + // optional field is nullable. + @MessagePackOptional + public String school; + } + + + @Test + public void testBackwardCompatibility() throws Exception { + MyClassVersion1 v1 = new MyClassVersion1(); + v1.name = "Sadayuki Furuhashi"; + v1.nickname = "frsyuki"; + + byte[] bytes = MessagePack.pack(v1); + + MyClassVersion2 v2 = MessagePack.unpack(bytes, MyClassVersion2.class); + + assertEquals(v1.name, v2.name); + assertEquals(v1.nickname, v2.nickname); + assertEquals(v2.age, -1); + } + + @Test + public void testForwardCompatibility() throws Exception { + MyClassVersion2 v2 = new MyClassVersion2(); + v2.name = "Sadayuki Furuhashi"; + v2.nickname = "frsyuki"; + v2.age = 23; + + byte[] bytes = MessagePack.pack(v2); + + MyClassVersion1 v1 = MessagePack.unpack(bytes, MyClassVersion1.class); + + assertEquals(v2.name, v1.name); + assertEquals(v2.nickname, v1.nickname); + } + + @Test + public void testNullFields01() throws Exception { + MyClassVersion1 src = new MyClassVersion1(); + src.name = "Sadayuki Furuhashi"; + src.nickname = null; + + byte[] bytes = MessagePack.pack(src); + + MyClassVersion1 dst = MessagePack.unpack(bytes, MyClassVersion1.class); + + assertEquals(dst.name, src.name); + assertEquals(dst.nickname, src.nickname); + } + + @Test + public void testNullFields02() throws Exception { + MyClassVersion1 src = new MyClassVersion1(); + src.name = null; + src.nickname = "frsyuki"; + + try { + byte[] bytes = MessagePack.pack(src); + } catch (Exception e) { + assertTrue(true); + return; + } + assertTrue(false); + } + + @Test + public void testNullFields03() throws Exception { + List src = new ArrayList(); + src.add(null); + src.add("frsyuki"); + + byte[] bytes = MessagePack.pack(src); + + try { + MyClassVersion1 dst = MessagePack.unpack(bytes, MyClassVersion1.class); + } catch (Exception e) { + assertTrue(true); + return; + } + assertTrue(false); + } + + @Test + public void testNullFields04() throws Exception { + MyClassVersion3 src = new MyClassVersion3(); + src.name = "Sadayuki Furuhashi"; + src.nickname = null; + src.age = 23; + src.school = null; + + byte[] bytes = MessagePack.pack(src); + + MyClassVersion3 dst = MessagePack.unpack(bytes, MyClassVersion3.class); + + assertEquals(dst.name, src.name); + assertEquals(dst.nickname, src.nickname); + } +} +