Skip to content

Commit 796193d

Browse files
authored
Improve versioning support documentation and validate version (#2214)
1 parent 2860908 commit 796193d

File tree

7 files changed

+180
-64
lines changed

7 files changed

+180
-64
lines changed

gson/src/main/java/com/google/gson/GsonBuilder.java

+18-5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES;
2929
import static com.google.gson.Gson.DEFAULT_USE_JDK_UNSAFE;
3030

31+
import com.google.gson.annotations.Since;
32+
import com.google.gson.annotations.Until;
3133
import com.google.gson.internal.$Gson$Preconditions;
3234
import com.google.gson.internal.Excluder;
3335
import com.google.gson.internal.bind.DefaultDateTypeAdapter;
@@ -143,14 +145,25 @@ public GsonBuilder() {
143145
}
144146

145147
/**
146-
* Configures Gson to enable versioning support.
148+
* Configures Gson to enable versioning support. Versioning support works based on the
149+
* annotation types {@link Since} and {@link Until}. It allows including or excluding fields
150+
* and classes based on the specified version. See the documentation of these annotation
151+
* types for more information.
147152
*
148-
* @param ignoreVersionsAfter any field or type marked with a version higher than this value
149-
* are ignored during serialization or deserialization.
153+
* <p>By default versioning support is disabled and usage of {@code @Since} and {@code @Until}
154+
* has no effect.
155+
*
156+
* @param version the version number to use.
150157
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
158+
* @throws IllegalArgumentException if the version number is NaN or negative
159+
* @see Since
160+
* @see Until
151161
*/
152-
public GsonBuilder setVersion(double ignoreVersionsAfter) {
153-
excluder = excluder.withVersion(ignoreVersionsAfter);
162+
public GsonBuilder setVersion(double version) {
163+
if (Double.isNaN(version) || version < 0.0) {
164+
throw new IllegalArgumentException("Invalid version: " + version);
165+
}
166+
excluder = excluder.withVersion(version);
154167
return this;
155168
}
156169

gson/src/main/java/com/google/gson/annotations/Since.java

+7-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.gson.annotations;
1818

19+
import com.google.gson.GsonBuilder;
1920
import java.lang.annotation.Documented;
2021
import java.lang.annotation.ElementType;
2122
import java.lang.annotation.Retention;
@@ -24,12 +25,11 @@
2425

2526
/**
2627
* An annotation that indicates the version number since a member or a type has been present.
27-
* This annotation is useful to manage versioning of your Json classes for a web-service.
28+
* This annotation is useful to manage versioning of your JSON classes for a web-service.
2829
*
2930
* <p>
3031
* This annotation has no effect unless you build {@link com.google.gson.Gson} with a
31-
* {@link com.google.gson.GsonBuilder} and invoke
32-
* {@link com.google.gson.GsonBuilder#setVersion(double)} method.
32+
* {@code GsonBuilder} and invoke the {@link GsonBuilder#setVersion(double)} method.
3333
*
3434
* <p>Here is an example of how this annotation is meant to be used:</p>
3535
* <pre>
@@ -50,14 +50,16 @@
5050
*
5151
* @author Inderjeet Singh
5252
* @author Joel Leitch
53+
* @see GsonBuilder#setVersion(double)
54+
* @see Until
5355
*/
5456
@Documented
5557
@Retention(RetentionPolicy.RUNTIME)
5658
@Target({ElementType.FIELD, ElementType.TYPE})
5759
public @interface Since {
5860
/**
59-
* the value indicating a version number since this member
60-
* or type has been present.
61+
* The value indicating a version number since this member or type has been present.
62+
* The number is inclusive; annotated elements will be included if {@code gsonVersion >= value}.
6163
*/
6264
double value();
6365
}

gson/src/main/java/com/google/gson/annotations/Until.java

+10-8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.gson.annotations;
1818

19+
import com.google.gson.GsonBuilder;
1920
import java.lang.annotation.Documented;
2021
import java.lang.annotation.ElementType;
2122
import java.lang.annotation.Retention;
@@ -24,14 +25,13 @@
2425

2526
/**
2627
* An annotation that indicates the version number until a member or a type should be present.
27-
* Basically, if Gson is created with a version number that exceeds the value stored in the
28-
* {@code Until} annotation then the field will be ignored from the JSON output. This annotation
29-
* is useful to manage versioning of your JSON classes for a web-service.
28+
* Basically, if Gson is created with a version number that is equal to or exceeds the value
29+
* stored in the {@code Until} annotation then the field will be ignored from the JSON output.
30+
* This annotation is useful to manage versioning of your JSON classes for a web-service.
3031
*
3132
* <p>
3233
* This annotation has no effect unless you build {@link com.google.gson.Gson} with a
33-
* {@link com.google.gson.GsonBuilder} and invoke
34-
* {@link com.google.gson.GsonBuilder#setVersion(double)} method.
34+
* {@code GsonBuilder} and invoke the {@link GsonBuilder#setVersion(double)} method.
3535
*
3636
* <p>Here is an example of how this annotation is meant to be used:</p>
3737
* <pre>
@@ -47,12 +47,14 @@
4747
* methods will use all the fields for serialization and deserialization. However, if you created
4848
* Gson with {@code Gson gson = new GsonBuilder().setVersion(1.2).create()} then the
4949
* {@code toJson()} and {@code fromJson()} methods of Gson will exclude the {@code emailAddress}
50-
* and {@code password} fields from the example above, because the version number passed to the
50+
* and {@code password} fields from the example above, because the version number passed to the
5151
* GsonBuilder, {@code 1.2}, exceeds the version number set on the {@code Until} annotation,
5252
* {@code 1.1}, for those fields.
5353
*
5454
* @author Inderjeet Singh
5555
* @author Joel Leitch
56+
* @see GsonBuilder#setVersion(double)
57+
* @see Since
5658
* @since 1.3
5759
*/
5860
@Documented
@@ -61,8 +63,8 @@
6163
public @interface Until {
6264

6365
/**
64-
* the value indicating a version number until this member
65-
* or type should be ignored.
66+
* The value indicating a version number until this member or type should be be included.
67+
* The number is exclusive; annotated elements will be included if {@code gsonVersion < value}.
6668
*/
6769
double value();
6870
}

gson/src/main/java/com/google/gson/internal/Excluder.java

+2-6
Original file line numberDiff line numberDiff line change
@@ -240,19 +240,15 @@ private boolean isValidVersion(Since since, Until until) {
240240
private boolean isValidSince(Since annotation) {
241241
if (annotation != null) {
242242
double annotationVersion = annotation.value();
243-
if (annotationVersion > version) {
244-
return false;
245-
}
243+
return version >= annotationVersion;
246244
}
247245
return true;
248246
}
249247

250248
private boolean isValidUntil(Until annotation) {
251249
if (annotation != null) {
252250
double annotationVersion = annotation.value();
253-
if (annotationVersion <= version) {
254-
return false;
255-
}
251+
return version < annotationVersion;
256252
}
257253
return true;
258254
}

gson/src/test/java/com/google/gson/GsonBuilderTest.java

+31-2
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,25 @@
1616

1717
package com.google.gson;
1818

19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertNotNull;
21+
import static org.junit.Assert.assertNotSame;
22+
import static org.junit.Assert.fail;
23+
1924
import com.google.gson.stream.JsonReader;
2025
import com.google.gson.stream.JsonWriter;
2126
import java.io.IOException;
2227
import java.lang.reflect.Field;
2328
import java.lang.reflect.Modifier;
2429
import java.lang.reflect.Type;
25-
import junit.framework.TestCase;
30+
import org.junit.Test;
2631

2732
/**
2833
* Unit tests for {@link GsonBuilder}.
2934
*
3035
* @author Inderjeet Singh
3136
*/
32-
public class GsonBuilderTest extends TestCase {
37+
public class GsonBuilderTest {
3338
private static final TypeAdapter<Object> NULL_TYPE_ADAPTER = new TypeAdapter<Object>() {
3439
@Override public void write(JsonWriter out, Object value) {
3540
throw new AssertionError();
@@ -39,6 +44,7 @@ public class GsonBuilderTest extends TestCase {
3944
}
4045
};
4146

47+
@Test
4248
public void testCreatingMoreThanOnce() {
4349
GsonBuilder builder = new GsonBuilder();
4450
Gson gson = builder.create();
@@ -61,6 +67,7 @@ public void testCreatingMoreThanOnce() {
6167
* Gson instances should not be affected by subsequent modification of GsonBuilder
6268
* which created them.
6369
*/
70+
@Test
6471
public void testModificationAfterCreate() {
6572
GsonBuilder gsonBuilder = new GsonBuilder();
6673
Gson gson = gsonBuilder.create();
@@ -136,6 +143,7 @@ public CustomClass3() {
136143
}
137144
}
138145

146+
@Test
139147
public void testExcludeFieldsWithModifiers() {
140148
Gson gson = new GsonBuilder()
141149
.excludeFieldsWithModifiers(Modifier.VOLATILE, Modifier.PRIVATE)
@@ -151,6 +159,7 @@ static class HasModifiers {
151159
String d = "d";
152160
}
153161

162+
@Test
154163
public void testTransientFieldExclusion() {
155164
Gson gson = new GsonBuilder()
156165
.excludeFieldsWithModifiers()
@@ -162,6 +171,7 @@ static class HasTransients {
162171
transient String a = "a";
163172
}
164173

174+
@Test
165175
public void testRegisterTypeAdapterForCoreType() {
166176
Type[] types = {
167177
byte.class,
@@ -176,6 +186,7 @@ public void testRegisterTypeAdapterForCoreType() {
176186
}
177187
}
178188

189+
@Test
179190
public void testDisableJdkUnsafe() {
180191
Gson gson = new GsonBuilder()
181192
.disableJdkUnsafe()
@@ -198,4 +209,22 @@ private static class ClassWithoutNoArgsConstructor {
198209
public ClassWithoutNoArgsConstructor(String s) {
199210
}
200211
}
212+
213+
@Test
214+
public void testSetVersionInvalid() {
215+
GsonBuilder builder = new GsonBuilder();
216+
try {
217+
builder.setVersion(Double.NaN);
218+
fail();
219+
} catch (IllegalArgumentException e) {
220+
assertEquals("Invalid version: NaN", e.getMessage());
221+
}
222+
223+
try {
224+
builder.setVersion(-0.1);
225+
fail();
226+
} catch (IllegalArgumentException e) {
227+
assertEquals("Invalid version: -0.1", e.getMessage());
228+
}
229+
}
201230
}

gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java

+56-14
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,82 @@
1616

1717
package com.google.gson;
1818

19+
import static org.junit.Assert.assertFalse;
20+
import static org.junit.Assert.assertTrue;
21+
1922
import com.google.gson.annotations.Since;
23+
import com.google.gson.annotations.Until;
2024
import com.google.gson.internal.Excluder;
21-
import junit.framework.TestCase;
25+
import org.junit.Test;
2226

2327
/**
2428
* Unit tests for the {@link Excluder} class.
2529
*
2630
* @author Joel Leitch
2731
*/
28-
public class VersionExclusionStrategyTest extends TestCase {
32+
public class VersionExclusionStrategyTest {
2933
private static final double VERSION = 5.0D;
3034

31-
public void testClassAndFieldAreAtSameVersion() throws Exception {
35+
@Test
36+
public void testSameVersion() throws Exception {
3237
Excluder excluder = Excluder.DEFAULT.withVersion(VERSION);
33-
assertFalse(excluder.excludeClass(MockObject.class, true));
34-
assertFalse(excluder.excludeField(MockObject.class.getField("someField"), true));
38+
assertFalse(excluder.excludeClass(MockClassSince.class, true));
39+
assertFalse(excluder.excludeField(MockClassSince.class.getField("someField"), true));
40+
41+
// Until version is exclusive
42+
assertTrue(excluder.excludeClass(MockClassUntil.class, true));
43+
assertTrue(excluder.excludeField(MockClassUntil.class.getField("someField"), true));
44+
45+
assertFalse(excluder.excludeClass(MockClassBoth.class, true));
46+
assertFalse(excluder.excludeField(MockClassBoth.class.getField("someField"), true));
3547
}
3648

37-
public void testClassAndFieldAreBehindInVersion() throws Exception {
38-
Excluder excluder = Excluder.DEFAULT.withVersion(VERSION + 1);
39-
assertFalse(excluder.excludeClass(MockObject.class, true));
40-
assertFalse(excluder.excludeField(MockObject.class.getField("someField"), true));
49+
@Test
50+
public void testNewerVersion() throws Exception {
51+
Excluder excluder = Excluder.DEFAULT.withVersion(VERSION + 5);
52+
assertFalse(excluder.excludeClass(MockClassSince.class, true));
53+
assertFalse(excluder.excludeField(MockClassSince.class.getField("someField"), true));
54+
55+
assertTrue(excluder.excludeClass(MockClassUntil.class, true));
56+
assertTrue(excluder.excludeField(MockClassUntil.class.getField("someField"), true));
57+
58+
assertTrue(excluder.excludeClass(MockClassBoth.class, true));
59+
assertTrue(excluder.excludeField(MockClassBoth.class.getField("someField"), true));
60+
}
61+
62+
@Test
63+
public void testOlderVersion() throws Exception {
64+
Excluder excluder = Excluder.DEFAULT.withVersion(VERSION - 5);
65+
assertTrue(excluder.excludeClass(MockClassSince.class, true));
66+
assertTrue(excluder.excludeField(MockClassSince.class.getField("someField"), true));
67+
68+
assertFalse(excluder.excludeClass(MockClassUntil.class, true));
69+
assertFalse(excluder.excludeField(MockClassUntil.class.getField("someField"), true));
70+
71+
assertTrue(excluder.excludeClass(MockClassBoth.class, true));
72+
assertTrue(excluder.excludeField(MockClassBoth.class.getField("someField"), true));
4173
}
4274

43-
public void testClassAndFieldAreAheadInVersion() throws Exception {
44-
Excluder excluder = Excluder.DEFAULT.withVersion(VERSION - 1);
45-
assertTrue(excluder.excludeClass(MockObject.class, true));
46-
assertTrue(excluder.excludeField(MockObject.class.getField("someField"), true));
75+
@Since(VERSION)
76+
private static class MockClassSince {
77+
78+
@Since(VERSION)
79+
public final int someField = 0;
80+
}
81+
82+
@Until(VERSION)
83+
private static class MockClassUntil {
84+
85+
@Until(VERSION)
86+
public final int someField = 0;
4787
}
4888

4989
@Since(VERSION)
50-
private static class MockObject {
90+
@Until(VERSION + 2)
91+
private static class MockClassBoth {
5192

5293
@Since(VERSION)
94+
@Until(VERSION + 2)
5395
public final int someField = 0;
5496
}
5597
}

0 commit comments

Comments
 (0)