From e2296f42d553e3f299c7dd2f4d1a4efd60007ade Mon Sep 17 00:00:00 2001 From: Mike Cumings Date: Tue, 25 Sep 2018 16:09:48 -0700 Subject: [PATCH 01/90] Fix issue with recursive type variable protections to fix #1390 When a type variable is referenced multiple times it needs to resolve to the same value. Previously, the second attempt would abort resolution early in order to protect against infinite recursion. --- .../com/google/gson/internal/$Gson$Types.java | 53 ++++++++++++------ .../ReusedTypeVariablesFullyResolveTest.java | 54 +++++++++++++++++++ 2 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 gson/src/test/java/com/google/gson/functional/ReusedTypeVariablesFullyResolveTest.java diff --git a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java index adea605f59..42344200d1 100644 --- a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java +++ b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java @@ -25,7 +25,12 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Properties; import static com.google.gson.internal.$Gson$Preconditions.checkArgument; import static com.google.gson.internal.$Gson$Preconditions.checkNotNull; @@ -334,41 +339,50 @@ public static Type[] getMapKeyAndValueTypes(Type context, Class contextRawTyp } public static Type resolve(Type context, Class contextRawType, Type toResolve) { - return resolve(context, contextRawType, toResolve, new HashSet()); + return resolve(context, contextRawType, toResolve, new HashMap()); } private static Type resolve(Type context, Class contextRawType, Type toResolve, - Collection visitedTypeVariables) { + Map visitedTypeVariables) { // this implementation is made a little more complicated in an attempt to avoid object-creation + TypeVariable resolving = null; while (true) { if (toResolve instanceof TypeVariable) { TypeVariable typeVariable = (TypeVariable) toResolve; - if (visitedTypeVariables.contains(typeVariable)) { + Type previouslyResolved = visitedTypeVariables.get(typeVariable); + if (previouslyResolved != null) { // cannot reduce due to infinite recursion - return toResolve; - } else { - visitedTypeVariables.add(typeVariable); + return (previouslyResolved == Void.TYPE) ? toResolve : previouslyResolved; } + + // Insert a placeholder to mark the fact that we are in the process of resolving this type + visitedTypeVariables.put(typeVariable, Void.TYPE); + if (resolving == null) { + resolving = typeVariable; + } + toResolve = resolveTypeVariable(context, contextRawType, typeVariable); if (toResolve == typeVariable) { - return toResolve; + break; } } else if (toResolve instanceof Class && ((Class) toResolve).isArray()) { Class original = (Class) toResolve; Type componentType = original.getComponentType(); Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables); - return componentType == newComponentType + toResolve = componentType == newComponentType ? original : arrayOf(newComponentType); + break; } else if (toResolve instanceof GenericArrayType) { GenericArrayType original = (GenericArrayType) toResolve; Type componentType = original.getGenericComponentType(); Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables); - return componentType == newComponentType + toResolve = componentType == newComponentType ? original : arrayOf(newComponentType); + break; } else if (toResolve instanceof ParameterizedType) { ParameterizedType original = (ParameterizedType) toResolve; @@ -388,9 +402,10 @@ private static Type resolve(Type context, Class contextRawType, Type toResolv } } - return changed + toResolve = changed ? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args) : original; + break; } else if (toResolve instanceof WildcardType) { WildcardType original = (WildcardType) toResolve; @@ -400,20 +415,28 @@ private static Type resolve(Type context, Class contextRawType, Type toResolv if (originalLowerBound.length == 1) { Type lowerBound = resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables); if (lowerBound != originalLowerBound[0]) { - return supertypeOf(lowerBound); + toResolve = supertypeOf(lowerBound); + break; } } else if (originalUpperBound.length == 1) { Type upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables); if (upperBound != originalUpperBound[0]) { - return subtypeOf(upperBound); + toResolve = subtypeOf(upperBound); + break; } } - return original; + toResolve = original; + break; } else { - return toResolve; + break; } } + // ensure that any in-process resolution gets updated with the final result + if (resolving != null) { + visitedTypeVariables.put(resolving, toResolve); + } + return toResolve; } static Type resolveTypeVariable(Type context, Class contextRawType, TypeVariable unknown) { diff --git a/gson/src/test/java/com/google/gson/functional/ReusedTypeVariablesFullyResolveTest.java b/gson/src/test/java/com/google/gson/functional/ReusedTypeVariablesFullyResolveTest.java new file mode 100644 index 0000000000..e3ddd840e9 --- /dev/null +++ b/gson/src/test/java/com/google/gson/functional/ReusedTypeVariablesFullyResolveTest.java @@ -0,0 +1,54 @@ +package com.google.gson.functional; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import static org.junit.Assert.*; + +/** + * This test covers the scenario described in #1390 where a type variable needs to be used + * by a type definition multiple times. Both type variable references should resolve to the + * same underlying concrete type. + */ +public class ReusedTypeVariablesFullyResolveTest { + + private Gson gson; + + @Before + public void setUp() { + gson = new GsonBuilder().create(); + } + + @SuppressWarnings("ConstantConditions") // The instances were being unmarshaled as Strings instead of TestEnums + @Test + public void testGenericsPreservation() { + TestEnumSetCollection withSet = gson.fromJson("{\"collection\":[\"ONE\",\"THREE\"]}", TestEnumSetCollection.class); + Iterator iterator = withSet.collection.iterator(); + assertNotNull(withSet); + assertNotNull(withSet.collection); + assertEquals(2, withSet.collection.size()); + TestEnum first = iterator.next(); + TestEnum second = iterator.next(); + + assertTrue(first instanceof TestEnum); + assertTrue(second instanceof TestEnum); + } + + enum TestEnum { ONE, TWO, THREE } + + private static class TestEnumSetCollection extends SetCollection {} + + private static class SetCollection extends BaseCollection> {} + + private static class BaseCollection> + { + public C collection; + } + +} From 69f7c4e243c385b318ed63205817347e4bbe379e Mon Sep 17 00:00:00 2001 From: Mike Cumings Date: Wed, 26 Sep 2018 22:38:53 -0700 Subject: [PATCH 02/90] Replace instance equality checks in $Gson$Types#resolve --- .../main/java/com/google/gson/internal/$Gson$Types.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java index 42344200d1..53985bc30a 100644 --- a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java +++ b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java @@ -370,7 +370,7 @@ private static Type resolve(Type context, Class contextRawType, Type toResolv Class original = (Class) toResolve; Type componentType = original.getComponentType(); Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables); - toResolve = componentType == newComponentType + toResolve = equal(componentType, newComponentType) ? original : arrayOf(newComponentType); break; @@ -379,7 +379,7 @@ private static Type resolve(Type context, Class contextRawType, Type toResolv GenericArrayType original = (GenericArrayType) toResolve; Type componentType = original.getGenericComponentType(); Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables); - toResolve = componentType == newComponentType + toResolve = equal(componentType, newComponentType) ? original : arrayOf(newComponentType); break; @@ -388,12 +388,12 @@ private static Type resolve(Type context, Class contextRawType, Type toResolv ParameterizedType original = (ParameterizedType) toResolve; Type ownerType = original.getOwnerType(); Type newOwnerType = resolve(context, contextRawType, ownerType, visitedTypeVariables); - boolean changed = newOwnerType != ownerType; + boolean changed = !equal(newOwnerType, ownerType); Type[] args = original.getActualTypeArguments(); for (int t = 0, length = args.length; t < length; t++) { Type resolvedTypeArgument = resolve(context, contextRawType, args[t], visitedTypeVariables); - if (resolvedTypeArgument != args[t]) { + if (!equal(resolvedTypeArgument, args[t])) { if (!changed) { args = args.clone(); changed = true; From 541252a9fb9837eb8867c094adcd37af2a655731 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Wed, 12 Feb 2020 19:25:56 +0100 Subject: [PATCH 03/90] Implement DefaultDateTypeAdapter in a type safer way --- .../google/gson/DefaultDateTypeAdapter.java | 87 +++++++++++-------- .../java/com/google/gson/GsonBuilder.java | 15 ++-- .../gson/DefaultDateTypeAdapterTest.java | 70 +++++++-------- 3 files changed, 94 insertions(+), 78 deletions(-) diff --git a/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java b/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java index 522963795a..81b809be69 100644 --- a/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java @@ -41,11 +41,52 @@ * @author Inderjeet Singh * @author Joel Leitch */ -final class DefaultDateTypeAdapter extends TypeAdapter { - +final class DefaultDateTypeAdapter extends TypeAdapter { private static final String SIMPLE_NAME = "DefaultDateTypeAdapter"; - private final Class dateType; + static abstract class DateType { + private DateType() { + } + + public static final DateType DATE = new DateType() { + @Override + protected Date deserialize(Date date) { + return date; + } + }; + public static final DateType SQL_DATE = new DateType() { + @Override + protected java.sql.Date deserialize(Date date) { + return new java.sql.Date(date.getTime()); + } + }; + public static final DateType SQL_TIMESTAMP = new DateType() { + @Override + protected Timestamp deserialize(Date date) { + return new Timestamp(date.getTime()); + } + }; + + protected abstract T deserialize(Date date); + + public DefaultDateTypeAdapter createAdapter(String datePattern) { + return new DefaultDateTypeAdapter(this, datePattern); + } + + public DefaultDateTypeAdapter createAdapter(int style) { + return new DefaultDateTypeAdapter(this, style); + } + + public DefaultDateTypeAdapter createAdapter(int dateStyle, int timeStyle) { + return new DefaultDateTypeAdapter(this, dateStyle, timeStyle); + } + + public DefaultDateTypeAdapter createDefaultsAdapter() { + return new DefaultDateTypeAdapter(this, DateFormat.DEFAULT, DateFormat.DEFAULT); + } + } + + private final DateType dateType; /** * List of 1 or more different date formats used for de-serialization attempts. @@ -53,18 +94,7 @@ final class DefaultDateTypeAdapter extends TypeAdapter { */ private final List dateFormats = new ArrayList(); - DefaultDateTypeAdapter(Class dateType) { - this.dateType = verifyDateType(dateType); - dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US)); - if (!Locale.getDefault().equals(Locale.US)) { - dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT)); - } - if (JavaVersion.isJava9OrLater()) { - dateFormats.add(PreJava9DateFormatProvider.getUSDateTimeFormat(DateFormat.DEFAULT, DateFormat.DEFAULT)); - } - } - - DefaultDateTypeAdapter(Class dateType, String datePattern) { + private DefaultDateTypeAdapter(DateType dateType, String datePattern) { this.dateType = verifyDateType(dateType); dateFormats.add(new SimpleDateFormat(datePattern, Locale.US)); if (!Locale.getDefault().equals(Locale.US)) { @@ -72,7 +102,7 @@ final class DefaultDateTypeAdapter extends TypeAdapter { } } - DefaultDateTypeAdapter(Class dateType, int style) { + private DefaultDateTypeAdapter(DateType dateType, int style) { this.dateType = verifyDateType(dateType); dateFormats.add(DateFormat.getDateInstance(style, Locale.US)); if (!Locale.getDefault().equals(Locale.US)) { @@ -83,11 +113,7 @@ final class DefaultDateTypeAdapter extends TypeAdapter { } } - public DefaultDateTypeAdapter(int dateStyle, int timeStyle) { - this(Date.class, dateStyle, timeStyle); - } - - public DefaultDateTypeAdapter(Class dateType, int dateStyle, int timeStyle) { + private DefaultDateTypeAdapter(DateType dateType, int dateStyle, int timeStyle) { this.dateType = verifyDateType(dateType); dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US)); if (!Locale.getDefault().equals(Locale.US)) { @@ -98,9 +124,9 @@ public DefaultDateTypeAdapter(Class dateType, int dateStyle, int } } - private static Class verifyDateType(Class dateType) { - if ( dateType != Date.class && dateType != java.sql.Date.class && dateType != Timestamp.class ) { - throw new IllegalArgumentException("Date type must be one of " + Date.class + ", " + Timestamp.class + ", or " + java.sql.Date.class + " but was " + dateType); + private static DateType verifyDateType(DateType dateType) { + if (dateType == null) { + throw new NullPointerException("dateType == null"); } return dateType; } @@ -120,22 +146,13 @@ public void write(JsonWriter out, Date value) throws IOException { } @Override - public Date read(JsonReader in) throws IOException { + public T read(JsonReader in) throws IOException { if (in.peek() == JsonToken.NULL) { in.nextNull(); return null; } Date date = deserializeToDate(in.nextString()); - if (dateType == Date.class) { - return date; - } else if (dateType == Timestamp.class) { - return new Timestamp(date.getTime()); - } else if (dateType == java.sql.Date.class) { - return new java.sql.Date(date.getTime()); - } else { - // This must never happen: dateType is guarded in the primary constructor - throw new AssertionError(); - } + return dateType.deserialize(date); } private Date deserializeToDate(String s) { diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index b97be452be..2c8dbe031b 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -602,20 +602,19 @@ public Gson create() { this.factories, this.hierarchyFactories, factories); } - @SuppressWarnings("unchecked") private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle, List factories) { - DefaultDateTypeAdapter dateTypeAdapter; + DefaultDateTypeAdapter dateTypeAdapter; TypeAdapter timestampTypeAdapter; TypeAdapter javaSqlDateTypeAdapter; if (datePattern != null && !"".equals(datePattern.trim())) { - dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, datePattern); - timestampTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(Timestamp.class, datePattern); - javaSqlDateTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(java.sql.Date.class, datePattern); + dateTypeAdapter = DefaultDateTypeAdapter.DateType.DATE.createAdapter(datePattern); + timestampTypeAdapter = DefaultDateTypeAdapter.DateType.SQL_TIMESTAMP.createAdapter(datePattern); + javaSqlDateTypeAdapter = DefaultDateTypeAdapter.DateType.SQL_DATE.createAdapter(datePattern); } else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) { - dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, dateStyle, timeStyle); - timestampTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(Timestamp.class, dateStyle, timeStyle); - javaSqlDateTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(java.sql.Date.class, dateStyle, timeStyle); + dateTypeAdapter = DefaultDateTypeAdapter.DateType.DATE.createAdapter(dateStyle, timeStyle); + timestampTypeAdapter = DefaultDateTypeAdapter.DateType.SQL_TIMESTAMP.createAdapter(dateStyle, timeStyle); + javaSqlDateTypeAdapter = DefaultDateTypeAdapter.DateType.SQL_DATE.createAdapter(dateStyle, timeStyle); } else { return; } diff --git a/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java b/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java index 632a482d8c..e626ea7aff 100644 --- a/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java +++ b/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java @@ -23,6 +23,7 @@ import java.util.Locale; import java.util.TimeZone; +import com.google.gson.DefaultDateTypeAdapter.DateType; import com.google.gson.internal.JavaVersion; import junit.framework.TestCase; @@ -52,18 +53,18 @@ private void assertFormattingAlwaysEmitsUsLocale(Locale locale) { String afterYearLongSep = JavaVersion.isJava9OrLater() ? " at " : " "; String utcFull = JavaVersion.isJava9OrLater() ? "Coordinated Universal Time" : "UTC"; assertFormatted(String.format("Jan 1, 1970%s12:00:00 AM", afterYearSep), - new DefaultDateTypeAdapter(Date.class)); - assertFormatted("1/1/70", new DefaultDateTypeAdapter(Date.class, DateFormat.SHORT)); - assertFormatted("Jan 1, 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.MEDIUM)); - assertFormatted("January 1, 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.LONG)); + DateType.DATE.createDefaultsAdapter()); + assertFormatted("1/1/70", DateType.DATE.createAdapter(DateFormat.SHORT)); + assertFormatted("Jan 1, 1970", DateType.DATE.createAdapter(DateFormat.MEDIUM)); + assertFormatted("January 1, 1970", DateType.DATE.createAdapter(DateFormat.LONG)); assertFormatted(String.format("1/1/70%s12:00 AM", afterYearSep), - new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT)); + DateType.DATE.createAdapter(DateFormat.SHORT, DateFormat.SHORT)); assertFormatted(String.format("Jan 1, 1970%s12:00:00 AM", afterYearSep), - new DefaultDateTypeAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM)); + DateType.DATE.createAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM)); assertFormatted(String.format("January 1, 1970%s12:00:00 AM UTC", afterYearLongSep), - new DefaultDateTypeAdapter(DateFormat.LONG, DateFormat.LONG)); + DateType.DATE.createAdapter(DateFormat.LONG, DateFormat.LONG)); assertFormatted(String.format("Thursday, January 1, 1970%s12:00:00 AM %s", afterYearLongSep, utcFull), - new DefaultDateTypeAdapter(DateFormat.FULL, DateFormat.FULL)); + DateType.DATE.createAdapter(DateFormat.FULL, DateFormat.FULL)); } finally { TimeZone.setDefault(defaultTimeZone); Locale.setDefault(defaultLocale); @@ -78,21 +79,21 @@ public void testParsingDatesFormattedWithSystemLocale() throws Exception { try { String afterYearSep = JavaVersion.isJava9OrLater() ? " à " : " "; assertParsed(String.format("1 janv. 1970%s00:00:00", afterYearSep), - new DefaultDateTypeAdapter(Date.class)); - assertParsed("01/01/70", new DefaultDateTypeAdapter(Date.class, DateFormat.SHORT)); - assertParsed("1 janv. 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.MEDIUM)); - assertParsed("1 janvier 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.LONG)); + DateType.DATE.createDefaultsAdapter()); + assertParsed("01/01/70", DateType.DATE.createAdapter(DateFormat.SHORT)); + assertParsed("1 janv. 1970", DateType.DATE.createAdapter(DateFormat.MEDIUM)); + assertParsed("1 janvier 1970", DateType.DATE.createAdapter(DateFormat.LONG)); assertParsed("01/01/70 00:00", - new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT)); + DateType.DATE.createAdapter(DateFormat.SHORT, DateFormat.SHORT)); assertParsed(String.format("1 janv. 1970%s00:00:00", afterYearSep), - new DefaultDateTypeAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM)); + DateType.DATE.createAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM)); assertParsed(String.format("1 janvier 1970%s00:00:00 UTC", afterYearSep), - new DefaultDateTypeAdapter(DateFormat.LONG, DateFormat.LONG)); + DateType.DATE.createAdapter(DateFormat.LONG, DateFormat.LONG)); assertParsed(JavaVersion.isJava9OrLater() ? (JavaVersion.getMajorJavaVersion() <11 ? "jeudi 1 janvier 1970 à 00:00:00 Coordinated Universal Time" : "jeudi 1 janvier 1970 à 00:00:00 Temps universel coordonné") : "jeudi 1 janvier 1970 00 h 00 UTC", - new DefaultDateTypeAdapter(DateFormat.FULL, DateFormat.FULL)); + DateType.DATE.createAdapter(DateFormat.FULL, DateFormat.FULL)); } finally { TimeZone.setDefault(defaultTimeZone); Locale.setDefault(defaultLocale); @@ -105,18 +106,18 @@ public void testParsingDatesFormattedWithUsLocale() throws Exception { Locale defaultLocale = Locale.getDefault(); Locale.setDefault(Locale.US); try { - assertParsed("Jan 1, 1970 0:00:00 AM", new DefaultDateTypeAdapter(Date.class)); - assertParsed("1/1/70", new DefaultDateTypeAdapter(Date.class, DateFormat.SHORT)); - assertParsed("Jan 1, 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.MEDIUM)); - assertParsed("January 1, 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.LONG)); + assertParsed("Jan 1, 1970 0:00:00 AM", DateType.DATE.createDefaultsAdapter()); + assertParsed("1/1/70", DateType.DATE.createAdapter(DateFormat.SHORT)); + assertParsed("Jan 1, 1970", DateType.DATE.createAdapter(DateFormat.MEDIUM)); + assertParsed("January 1, 1970", DateType.DATE.createAdapter(DateFormat.LONG)); assertParsed("1/1/70 0:00 AM", - new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT)); + DateType.DATE.createAdapter(DateFormat.SHORT, DateFormat.SHORT)); assertParsed("Jan 1, 1970 0:00:00 AM", - new DefaultDateTypeAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM)); + DateType.DATE.createAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM)); assertParsed("January 1, 1970 0:00:00 AM UTC", - new DefaultDateTypeAdapter(DateFormat.LONG, DateFormat.LONG)); + DateType.DATE.createAdapter(DateFormat.LONG, DateFormat.LONG)); assertParsed("Thursday, January 1, 1970 0:00:00 AM UTC", - new DefaultDateTypeAdapter(DateFormat.FULL, DateFormat.FULL)); + DateType.DATE.createAdapter(DateFormat.FULL, DateFormat.FULL)); } finally { TimeZone.setDefault(defaultTimeZone); Locale.setDefault(defaultLocale); @@ -131,8 +132,8 @@ public void testFormatUsesDefaultTimezone() throws Exception { try { String afterYearSep = JavaVersion.isJava9OrLater() ? ", " : " "; assertFormatted(String.format("Dec 31, 1969%s4:00:00 PM", afterYearSep), - new DefaultDateTypeAdapter(Date.class)); - assertParsed("Dec 31, 1969 4:00:00 PM", new DefaultDateTypeAdapter(Date.class)); + DateType.DATE.createDefaultsAdapter()); + assertParsed("Dec 31, 1969 4:00:00 PM", DateType.DATE.createDefaultsAdapter()); } finally { TimeZone.setDefault(defaultTimeZone); Locale.setDefault(defaultLocale); @@ -140,7 +141,7 @@ public void testFormatUsesDefaultTimezone() throws Exception { } public void testDateDeserializationISO8601() throws Exception { - DefaultDateTypeAdapter adapter = new DefaultDateTypeAdapter(Date.class); + DefaultDateTypeAdapter adapter = DateType.DATE.createDefaultsAdapter(); assertParsed("1970-01-01T00:00:00.000Z", adapter); assertParsed("1970-01-01T00:00Z", adapter); assertParsed("1970-01-01T00:00:00+00:00", adapter); @@ -150,7 +151,7 @@ public void testDateDeserializationISO8601() throws Exception { public void testDateSerialization() throws Exception { int dateStyle = DateFormat.LONG; - DefaultDateTypeAdapter dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, dateStyle); + DefaultDateTypeAdapter dateTypeAdapter = DateType.DATE.createAdapter(dateStyle); DateFormat formatter = DateFormat.getDateInstance(dateStyle, Locale.US); Date currentDate = new Date(); @@ -160,7 +161,7 @@ public void testDateSerialization() throws Exception { public void testDatePattern() throws Exception { String pattern = "yyyy-MM-dd"; - DefaultDateTypeAdapter dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, pattern); + DefaultDateTypeAdapter dateTypeAdapter = DateType.DATE.createAdapter(pattern); DateFormat formatter = new SimpleDateFormat(pattern); Date currentDate = new Date(); @@ -168,33 +169,32 @@ public void testDatePattern() throws Exception { assertEquals(toLiteral(formatter.format(currentDate)), dateString); } - @SuppressWarnings("unused") public void testInvalidDatePattern() throws Exception { try { - new DefaultDateTypeAdapter(Date.class, "I am a bad Date pattern...."); + DateType.DATE.createAdapter("I am a bad Date pattern...."); fail("Invalid date pattern should fail."); } catch (IllegalArgumentException expected) { } } public void testNullValue() throws Exception { - DefaultDateTypeAdapter adapter = new DefaultDateTypeAdapter(Date.class); + DefaultDateTypeAdapter adapter = DateType.DATE.createDefaultsAdapter(); assertNull(adapter.fromJson("null")); assertEquals("null", adapter.toJson(null)); } public void testUnexpectedToken() throws Exception { try { - DefaultDateTypeAdapter adapter = new DefaultDateTypeAdapter(Date.class); + DefaultDateTypeAdapter adapter = DateType.DATE.createDefaultsAdapter(); adapter.fromJson("{}"); fail("Unexpected token should fail."); } catch (IllegalStateException expected) { } } - private void assertFormatted(String formatted, DefaultDateTypeAdapter adapter) { + private void assertFormatted(String formatted, DefaultDateTypeAdapter adapter) { assertEquals(toLiteral(formatted), adapter.toJson(new Date(0))); } - private void assertParsed(String date, DefaultDateTypeAdapter adapter) throws IOException { + private void assertParsed(String date, DefaultDateTypeAdapter adapter) throws IOException { assertEquals(date, new Date(0), adapter.fromJson(toLiteral(date))); assertEquals("ISO 8601", new Date(0), adapter.fromJson(toLiteral("1970-01-01T00:00:00Z"))); } From 361292f1c192cdf06195cfd5346763ba38e3ce0d Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Wed, 12 Feb 2020 19:26:06 +0100 Subject: [PATCH 04/90] Fix warnings --- .../src/main/java/com/google/gson/internal/$Gson$Types.java | 4 ++-- .../java/com/google/gson/functional/CollectionTest.java | 1 - .../com/google/gson/functional/TreeTypeAdaptersTest.java | 1 + .../com/google/gson/internal/LinkedHashTreeMapTest.java | 1 + .../java/com/google/gson/internal/LinkedTreeMapTest.java | 1 + .../gson/internal/bind/RecursiveTypesResolveTest.java | 6 +++++- 6 files changed, 10 insertions(+), 4 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java index adea605f59..bd55ef1e0f 100644 --- a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java +++ b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java @@ -334,11 +334,11 @@ public static Type[] getMapKeyAndValueTypes(Type context, Class contextRawTyp } public static Type resolve(Type context, Class contextRawType, Type toResolve) { - return resolve(context, contextRawType, toResolve, new HashSet()); + return resolve(context, contextRawType, toResolve, new HashSet>()); } private static Type resolve(Type context, Class contextRawType, Type toResolve, - Collection visitedTypeVariables) { + Collection> visitedTypeVariables) { // this implementation is made a little more complicated in an attempt to avoid object-creation while (true) { if (toResolve instanceof TypeVariable) { diff --git a/gson/src/test/java/com/google/gson/functional/CollectionTest.java b/gson/src/test/java/com/google/gson/functional/CollectionTest.java index 8aa36e21ea..261c5d02d3 100644 --- a/gson/src/test/java/com/google/gson/functional/CollectionTest.java +++ b/gson/src/test/java/com/google/gson/functional/CollectionTest.java @@ -37,7 +37,6 @@ import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; -import com.google.gson.common.MoreAsserts; import com.google.gson.common.TestTypes.BagOfPrimitives; import com.google.gson.reflect.TypeToken; diff --git a/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java index ad737ec510..dd412ea131 100644 --- a/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java @@ -172,6 +172,7 @@ private static class HistoryCourse { int numClasses; } + @SafeVarargs private static List createList(T ...items) { return Arrays.asList(items); } diff --git a/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java b/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java index 2aeeeb7646..df595b796b 100644 --- a/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java +++ b/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java @@ -280,6 +280,7 @@ private String toString(Node root) { } } + @SafeVarargs private void assertIterationOrder(Iterable actual, T... expected) { ArrayList actualList = new ArrayList(); for (T t : actual) { diff --git a/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java b/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java index 580d25a571..058de3367f 100644 --- a/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java +++ b/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java @@ -140,6 +140,7 @@ public void testEqualsAndHashCode() throws Exception { MoreAsserts.assertEqualsAndHashCode(map1, map2); } + @SafeVarargs private void assertIterationOrder(Iterable actual, T... expected) { ArrayList actualList = new ArrayList(); for (T t : actual) { diff --git a/gson/src/test/java/com/google/gson/internal/bind/RecursiveTypesResolveTest.java b/gson/src/test/java/com/google/gson/internal/bind/RecursiveTypesResolveTest.java index 730183fbee..a2260c373f 100644 --- a/gson/src/test/java/com/google/gson/internal/bind/RecursiveTypesResolveTest.java +++ b/gson/src/test/java/com/google/gson/internal/bind/RecursiveTypesResolveTest.java @@ -36,7 +36,7 @@ public class RecursiveTypesResolveTest extends TestCase { @SuppressWarnings("unused") private static class Foo1 { - public Foo2 foo2; + public Foo2 foo2; } @SuppressWarnings("unused") private static class Foo2 { @@ -48,6 +48,7 @@ private static class Foo2 { */ public void testRecursiveResolveSimple() { + @SuppressWarnings("rawtypes") TypeAdapter adapter = new Gson().getAdapter(Foo1.class); assertNotNull(adapter); } @@ -62,6 +63,7 @@ public void testIssue603PrintStream() { } public void testIssue440WeakReference() throws Exception { + @SuppressWarnings("rawtypes") TypeAdapter adapter = new Gson().getAdapter(WeakReference.class); assertNotNull(adapter); } @@ -105,11 +107,13 @@ private static class TestType2 { } public void testRecursiveTypeVariablesResolve1() throws Exception { + @SuppressWarnings("rawtypes") TypeAdapter adapter = new Gson().getAdapter(TestType.class); assertNotNull(adapter); } public void testRecursiveTypeVariablesResolve12() throws Exception { + @SuppressWarnings("rawtypes") TypeAdapter adapter = new Gson().getAdapter(TestType2.class); assertNotNull(adapter); } From 380c4ec12c7a8b2b85cc3a8ac78b3f123162c70c Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sat, 9 May 2020 17:37:21 +0200 Subject: [PATCH 05/90] Make dependency on java.sql optional --- gson/src/main/java/com/google/gson/Gson.java | 13 ++- .../java/com/google/gson/GsonBuilder.java | 43 +++++--- .../bind}/DefaultDateTypeAdapter.java | 72 +++++++------- .../gson/internal/bind/TypeAdapters.java | 27 +---- .../{bind => sql}/SqlDateTypeAdapter.java | 9 +- .../SqlTimeTypeAdapter.java} | 13 ++- .../internal/sql/SqlTimestampTypeAdapter.java | 43 ++++++++ .../gson/internal/sql/SqlTypesSupport.java | 71 +++++++++++++ gson/src/main/java/module-info.java | 3 +- .../bind}/DefaultDateTypeAdapterTest.java | 99 +++++++++++-------- .../internal/sql/SqlTypesSupportTest.java | 16 +++ 11 files changed, 274 insertions(+), 135 deletions(-) rename gson/src/main/java/com/google/gson/{ => internal/bind}/DefaultDateTypeAdapter.java (73%) rename gson/src/main/java/com/google/gson/internal/{bind => sql}/SqlDateTypeAdapter.java (91%) rename gson/src/main/java/com/google/gson/internal/{bind/TimeTypeAdapter.java => sql/SqlTimeTypeAdapter.java} (86%) create mode 100644 gson/src/main/java/com/google/gson/internal/sql/SqlTimestampTypeAdapter.java create mode 100644 gson/src/main/java/com/google/gson/internal/sql/SqlTypesSupport.java rename gson/src/test/java/com/google/gson/{ => internal/bind}/DefaultDateTypeAdapterTest.java (64%) create mode 100644 gson/src/test/java/com/google/gson/internal/sql/SqlTypesSupportTest.java diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 27f3ee9246..a7938c84e6 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -49,9 +49,8 @@ import com.google.gson.internal.bind.MapTypeAdapterFactory; import com.google.gson.internal.bind.ObjectTypeAdapter; import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; -import com.google.gson.internal.bind.SqlDateTypeAdapter; -import com.google.gson.internal.bind.TimeTypeAdapter; import com.google.gson.internal.bind.TypeAdapters; +import com.google.gson.internal.sql.SqlTypesSupport; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; @@ -262,9 +261,13 @@ public Gson() { factories.add(TypeAdapters.BIT_SET_FACTORY); factories.add(DateTypeAdapter.FACTORY); factories.add(TypeAdapters.CALENDAR_FACTORY); - factories.add(TimeTypeAdapter.FACTORY); - factories.add(SqlDateTypeAdapter.FACTORY); - factories.add(TypeAdapters.TIMESTAMP_FACTORY); + + if (SqlTypesSupport.SUPPORTS_SQL_TYPES) { + factories.add(SqlTypesSupport.TIME_FACTORY); + factories.add(SqlTypesSupport.DATE_FACTORY); + factories.add(SqlTypesSupport.TIMESTAMP_FACTORY); + } + factories.add(ArrayTypeAdapter.FACTORY); factories.add(TypeAdapters.CLASS_FACTORY); diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index 2c8dbe031b..b2fd74edec 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -17,7 +17,6 @@ package com.google.gson; import java.lang.reflect.Type; -import java.sql.Timestamp; import java.text.DateFormat; import java.util.ArrayList; import java.util.Collections; @@ -28,8 +27,10 @@ import com.google.gson.internal.$Gson$Preconditions; import com.google.gson.internal.Excluder; +import com.google.gson.internal.bind.DefaultDateTypeAdapter; import com.google.gson.internal.bind.TreeTypeAdapter; import com.google.gson.internal.bind.TypeAdapters; +import com.google.gson.internal.sql.SqlTypesSupport; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; @@ -417,8 +418,8 @@ public GsonBuilder disableHtmlEscaping() { * call this method or {@link #setDateFormat(int)} multiple times, but only the last invocation * will be used to decide the serialization format. * - *

The date format will be used to serialize and deserialize {@link java.util.Date}, {@link - * java.sql.Timestamp} and {@link java.sql.Date}. + *

The date format will be used to serialize and deserialize {@link java.util.Date} and in case + * the {@code java.sql} module is present, also {@link java.sql.Timestamp} and {@link java.sql.Date}. * *

Note that this pattern must abide by the convention provided by {@code SimpleDateFormat} * class. See the documentation in {@link java.text.SimpleDateFormat} for more information on @@ -604,23 +605,33 @@ public Gson create() { private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle, List factories) { - DefaultDateTypeAdapter dateTypeAdapter; - TypeAdapter timestampTypeAdapter; - TypeAdapter javaSqlDateTypeAdapter; - if (datePattern != null && !"".equals(datePattern.trim())) { - dateTypeAdapter = DefaultDateTypeAdapter.DateType.DATE.createAdapter(datePattern); - timestampTypeAdapter = DefaultDateTypeAdapter.DateType.SQL_TIMESTAMP.createAdapter(datePattern); - javaSqlDateTypeAdapter = DefaultDateTypeAdapter.DateType.SQL_DATE.createAdapter(datePattern); + TypeAdapterFactory dateAdapterFactory; + boolean sqlTypesSupported = SqlTypesSupport.SUPPORTS_SQL_TYPES; + TypeAdapterFactory sqlTimestampAdapterFactory = null; + TypeAdapterFactory sqlDateAdapterFactory = null; + + if (datePattern != null && !datePattern.trim().isEmpty()) { + dateAdapterFactory = DefaultDateTypeAdapter.DateType.DATE.createAdapterFactory(datePattern); + + if (sqlTypesSupported) { + sqlTimestampAdapterFactory = SqlTypesSupport.TIMESTAMP_DATE_TYPE.createAdapterFactory(datePattern); + sqlDateAdapterFactory = SqlTypesSupport.DATE_DATE_TYPE.createAdapterFactory(datePattern); + } } else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) { - dateTypeAdapter = DefaultDateTypeAdapter.DateType.DATE.createAdapter(dateStyle, timeStyle); - timestampTypeAdapter = DefaultDateTypeAdapter.DateType.SQL_TIMESTAMP.createAdapter(dateStyle, timeStyle); - javaSqlDateTypeAdapter = DefaultDateTypeAdapter.DateType.SQL_DATE.createAdapter(dateStyle, timeStyle); + dateAdapterFactory = DefaultDateTypeAdapter.DateType.DATE.createAdapterFactory(dateStyle, timeStyle); + + if (sqlTypesSupported) { + sqlTimestampAdapterFactory = SqlTypesSupport.TIMESTAMP_DATE_TYPE.createAdapterFactory(dateStyle, timeStyle); + sqlDateAdapterFactory = SqlTypesSupport.DATE_DATE_TYPE.createAdapterFactory(dateStyle, timeStyle); + } } else { return; } - factories.add(TypeAdapters.newFactory(Date.class, dateTypeAdapter)); - factories.add(TypeAdapters.newFactory(Timestamp.class, timestampTypeAdapter)); - factories.add(TypeAdapters.newFactory(java.sql.Date.class, javaSqlDateTypeAdapter)); + factories.add(dateAdapterFactory); + if (sqlTypesSupported) { + factories.add(sqlTimestampAdapterFactory); + factories.add(sqlDateAdapterFactory); + } } } diff --git a/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/DefaultDateTypeAdapter.java similarity index 73% rename from gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java rename to gson/src/main/java/com/google/gson/internal/bind/DefaultDateTypeAdapter.java index 81b809be69..9ac948f784 100644 --- a/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/DefaultDateTypeAdapter.java @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.google.gson; +package com.google.gson.internal.bind; import java.io.IOException; -import java.sql.Timestamp; import java.text.DateFormat; import java.text.ParseException; import java.text.ParsePosition; @@ -27,6 +26,9 @@ import java.util.List; import java.util.Locale; +import com.google.gson.JsonSyntaxException; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; import com.google.gson.internal.JavaVersion; import com.google.gson.internal.PreJava9DateFormatProvider; import com.google.gson.internal.bind.util.ISO8601Utils; @@ -35,54 +37,49 @@ import com.google.gson.stream.JsonWriter; /** - * This type adapter supports three subclasses of date: Date, Timestamp, and - * java.sql.Date. + * This type adapter supports subclasses of date by defining a + * {@link DefaultDateTypeAdapter.DateType} and then using its {@code createAdapterFactory} + * methods. * * @author Inderjeet Singh * @author Joel Leitch */ -final class DefaultDateTypeAdapter extends TypeAdapter { +public final class DefaultDateTypeAdapter extends TypeAdapter { private static final String SIMPLE_NAME = "DefaultDateTypeAdapter"; - static abstract class DateType { - private DateType() { - } - - public static final DateType DATE = new DateType() { - @Override - protected Date deserialize(Date date) { + public static abstract class DateType { + public static final DateType DATE = new DateType(Date.class) { + @Override protected Date deserialize(Date date) { return date; } }; - public static final DateType SQL_DATE = new DateType() { - @Override - protected java.sql.Date deserialize(Date date) { - return new java.sql.Date(date.getTime()); - } - }; - public static final DateType SQL_TIMESTAMP = new DateType() { - @Override - protected Timestamp deserialize(Date date) { - return new Timestamp(date.getTime()); - } - }; + + private final Class dateClass; + + protected DateType(Class dateClass) { + this.dateClass = dateClass; + } protected abstract T deserialize(Date date); - public DefaultDateTypeAdapter createAdapter(String datePattern) { - return new DefaultDateTypeAdapter(this, datePattern); + private final TypeAdapterFactory createFactory(DefaultDateTypeAdapter adapter) { + return TypeAdapters.newFactory(dateClass, adapter); + } + + public final TypeAdapterFactory createAdapterFactory(String datePattern) { + return createFactory(new DefaultDateTypeAdapter(this, datePattern)); } - public DefaultDateTypeAdapter createAdapter(int style) { - return new DefaultDateTypeAdapter(this, style); + public final TypeAdapterFactory createAdapterFactory(int style) { + return createFactory(new DefaultDateTypeAdapter(this, style)); } - public DefaultDateTypeAdapter createAdapter(int dateStyle, int timeStyle) { - return new DefaultDateTypeAdapter(this, dateStyle, timeStyle); + public final TypeAdapterFactory createAdapterFactory(int dateStyle, int timeStyle) { + return createFactory(new DefaultDateTypeAdapter(this, dateStyle, timeStyle)); } - public DefaultDateTypeAdapter createDefaultsAdapter() { - return new DefaultDateTypeAdapter(this, DateFormat.DEFAULT, DateFormat.DEFAULT); + public final TypeAdapterFactory createDefaultsAdapterFactory() { + return createFactory(new DefaultDateTypeAdapter(this, DateFormat.DEFAULT, DateFormat.DEFAULT)); } } @@ -162,11 +159,12 @@ private Date deserializeToDate(String s) { return dateFormat.parse(s); } catch (ParseException ignored) {} } - try { - return ISO8601Utils.parse(s, new ParsePosition(0)); - } catch (ParseException e) { - throw new JsonSyntaxException(s, e); - } + } + + try { + return ISO8601Utils.parse(s, new ParsePosition(0)); + } catch (ParseException e) { + throw new JsonSyntaxException(s, e); } } diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index 354ce5a1fb..af790e2c2b 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -23,12 +23,10 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.BitSet; import java.util.Calendar; import java.util.Currency; -import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; @@ -406,7 +404,7 @@ public void write(JsonWriter out, String value) throws IOException { out.value(value); } }; - + public static final TypeAdapter BIG_DECIMAL = new TypeAdapter() { @Override public BigDecimal read(JsonReader in) throws IOException { if (in.peek() == JsonToken.NULL) { @@ -424,7 +422,7 @@ public void write(JsonWriter out, String value) throws IOException { out.value(value); } }; - + public static final TypeAdapter BIG_INTEGER = new TypeAdapter() { @Override public BigInteger read(JsonReader in) throws IOException { if (in.peek() == JsonToken.NULL) { @@ -569,27 +567,6 @@ public void write(JsonWriter out, Currency value) throws IOException { }.nullSafe(); public static final TypeAdapterFactory CURRENCY_FACTORY = newFactory(Currency.class, CURRENCY); - public static final TypeAdapterFactory TIMESTAMP_FACTORY = new TypeAdapterFactory() { - @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal - @Override public TypeAdapter create(Gson gson, TypeToken typeToken) { - if (typeToken.getRawType() != Timestamp.class) { - return null; - } - - final TypeAdapter dateTypeAdapter = gson.getAdapter(Date.class); - return (TypeAdapter) new TypeAdapter() { - @Override public Timestamp read(JsonReader in) throws IOException { - Date date = dateTypeAdapter.read(in); - return date != null ? new Timestamp(date.getTime()) : null; - } - - @Override public void write(JsonWriter out, Timestamp value) throws IOException { - dateTypeAdapter.write(out, value); - } - }; - } - }; - public static final TypeAdapter CALENDAR = new TypeAdapter() { private static final String YEAR = "year"; private static final String MONTH = "month"; diff --git a/gson/src/main/java/com/google/gson/internal/bind/SqlDateTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/sql/SqlDateTypeAdapter.java similarity index 91% rename from gson/src/main/java/com/google/gson/internal/bind/SqlDateTypeAdapter.java rename to gson/src/main/java/com/google/gson/internal/sql/SqlDateTypeAdapter.java index 5ec244f29e..b3da1fefd4 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/SqlDateTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/sql/SqlDateTypeAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.gson.internal.bind; +package com.google.gson.internal.sql; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; @@ -35,8 +35,8 @@ * this class state. DateFormat isn't thread safe either, so this class has * to synchronize its read and write methods. */ -public final class SqlDateTypeAdapter extends TypeAdapter { - public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { +final class SqlDateTypeAdapter extends TypeAdapter { + static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal @Override public TypeAdapter create(Gson gson, TypeToken typeToken) { return typeToken.getRawType() == java.sql.Date.class @@ -46,6 +46,9 @@ public final class SqlDateTypeAdapter extends TypeAdapter { private final DateFormat format = new SimpleDateFormat("MMM d, yyyy"); + private SqlDateTypeAdapter() { + } + @Override public synchronized java.sql.Date read(JsonReader in) throws IOException { if (in.peek() == JsonToken.NULL) { diff --git a/gson/src/main/java/com/google/gson/internal/bind/TimeTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/sql/SqlTimeTypeAdapter.java similarity index 86% rename from gson/src/main/java/com/google/gson/internal/bind/TimeTypeAdapter.java rename to gson/src/main/java/com/google/gson/internal/sql/SqlTimeTypeAdapter.java index 55d4b2f693..ee65726b4e 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TimeTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/sql/SqlTimeTypeAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.gson.internal.bind; +package com.google.gson.internal.sql; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; @@ -32,21 +32,24 @@ import java.util.Date; /** - * Adapter for Time. Although this class appears stateless, it is not. + * Adapter for java.sql.Time. Although this class appears stateless, it is not. * DateFormat captures its time zone and locale when it is created, which gives * this class state. DateFormat isn't thread safe either, so this class has * to synchronize its read and write methods. */ -public final class TimeTypeAdapter extends TypeAdapter

    + *
  • {@link Double} values are returned for JSON numbers if the deserialization type is declared as + * {@code Object}, see {@link ToNumberPolicy#DOUBLE};
  • + *
  • Lazily parsed number values are returned if the deserialization type is declared as {@code Number}, + * see {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}.
  • + *
+ * + *

For historical reasons, Gson does not support deserialization of arbitrary-length numbers for + * {@code Object} and {@code Number} by default, potentially causing precision loss. However, + * RFC 8259 permits this: + * + *

+ *   This specification allows implementations to set limits on the range
+ *   and precision of numbers accepted.  Since software that implements
+ *   IEEE 754 binary64 (double precision) numbers [IEEE754] is generally
+ *   available and widely used, good interoperability can be achieved by
+ *   implementations that expect no more precision or range than these
+ *   provide, in the sense that implementations will approximate JSON
+ *   numbers within the expected precision.  A JSON number such as 1E400
+ *   or 3.141592653589793238462643383279 may indicate potential
+ *   interoperability problems, since it suggests that the software that
+ *   created it expects receiving software to have greater capabilities
+ *   for numeric magnitude and precision than is widely available.
+ * 
+ * + *

To overcome the precision loss, use for example {@link ToNumberPolicy#LONG_OR_DOUBLE} or + * {@link ToNumberPolicy#BIG_DECIMAL}.

+ * + * @see ToNumberPolicy + * @see GsonBuilder#setObjectToNumberStrategy(ToNumberStrategy) + * @see GsonBuilder#setNumberToNumberStrategy(ToNumberStrategy) + */ +public interface ToNumberStrategy { + + /** + * Reads a number from the given JSON reader. A strategy is supposed to read a single value from the + * reader, and the read value is guaranteed never to be {@code null}. + * + * @param in JSON reader to read a number from + * @return number read from the JSON reader. + * @throws IOException + */ + public Number readNumber(JsonReader in) throws IOException; +} diff --git a/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java new file mode 100644 index 0000000000..f5efff2825 --- /dev/null +++ b/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 Google Inc. + * + * 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 com.google.gson.internal.bind; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.ToNumberStrategy; +import com.google.gson.ToNumberPolicy; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; + +/** + * Type adapter for {@link Number}. + */ +public final class NumberTypeAdapter extends TypeAdapter { + /** + * Gson default factory using {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}. + */ + private static final TypeAdapterFactory LAZILY_PARSED_NUMBER_FACTORY = newFactory(ToNumberPolicy.LAZILY_PARSED_NUMBER); + + private final ToNumberStrategy toNumberStrategy; + + private NumberTypeAdapter(ToNumberStrategy toNumberStrategy) { + this.toNumberStrategy = toNumberStrategy; + } + + private static TypeAdapterFactory newFactory(ToNumberStrategy toNumberStrategy) { + final NumberTypeAdapter adapter = new NumberTypeAdapter(toNumberStrategy); + return new TypeAdapterFactory() { + @SuppressWarnings("unchecked") + @Override public TypeAdapter create(Gson gson, TypeToken type) { + return type.getRawType() == Number.class ? (TypeAdapter) adapter : null; + } + }; + } + + public static TypeAdapterFactory getFactory(ToNumberStrategy toNumberStrategy) { + if (toNumberStrategy == ToNumberPolicy.LAZILY_PARSED_NUMBER) { + return LAZILY_PARSED_NUMBER_FACTORY; + } else { + return newFactory(toNumberStrategy); + } + } + + @Override public Number read(JsonReader in) throws IOException { + JsonToken jsonToken = in.peek(); + switch (jsonToken) { + case NULL: + in.nextNull(); + return null; + case NUMBER: + case STRING: + return toNumberStrategy.readNumber(in); + default: + throw new JsonSyntaxException("Expecting number, got: " + jsonToken); + } + } + + @Override public void write(JsonWriter out, Number value) throws IOException { + out.value(value); + } +} diff --git a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java index ec42e04826..b50f61e12e 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java @@ -17,6 +17,8 @@ package com.google.gson.internal.bind; import com.google.gson.Gson; +import com.google.gson.ToNumberStrategy; +import com.google.gson.ToNumberPolicy; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.internal.LinkedTreeMap; @@ -35,20 +37,37 @@ * serialization and a primitive/Map/List on deserialization. */ public final class ObjectTypeAdapter extends TypeAdapter { - public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { - @SuppressWarnings("unchecked") - @Override public TypeAdapter create(Gson gson, TypeToken type) { - if (type.getRawType() == Object.class) { - return (TypeAdapter) new ObjectTypeAdapter(gson); - } - return null; - } - }; + /** + * Gson default factory using {@link ToNumberPolicy#DOUBLE}. + */ + private static final TypeAdapterFactory DOUBLE_FACTORY = newFactory(ToNumberPolicy.DOUBLE); private final Gson gson; + private final ToNumberStrategy toNumberStrategy; - ObjectTypeAdapter(Gson gson) { + private ObjectTypeAdapter(Gson gson, ToNumberStrategy toNumberStrategy) { this.gson = gson; + this.toNumberStrategy = toNumberStrategy; + } + + private static TypeAdapterFactory newFactory(final ToNumberStrategy toNumberStrategy) { + return new TypeAdapterFactory() { + @SuppressWarnings("unchecked") + @Override public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getRawType() == Object.class) { + return (TypeAdapter) new ObjectTypeAdapter(gson, toNumberStrategy); + } + return null; + } + }; + } + + public static TypeAdapterFactory getFactory(ToNumberStrategy toNumberStrategy) { + if (toNumberStrategy == ToNumberPolicy.DOUBLE) { + return DOUBLE_FACTORY; + } else { + return newFactory(toNumberStrategy); + } } @Override public Object read(JsonReader in) throws IOException { @@ -76,7 +95,7 @@ public final class ObjectTypeAdapter extends TypeAdapter { return in.nextString(); case NUMBER: - return in.nextDouble(); + return toNumberStrategy.readNumber(in); case BOOLEAN: return in.nextBoolean(); diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index 81dda90359..cd5ba2e395 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -343,29 +343,6 @@ public void write(JsonWriter out, Number value) throws IOException { } }; - public static final TypeAdapter NUMBER = new TypeAdapter() { - @Override - public Number read(JsonReader in) throws IOException { - JsonToken jsonToken = in.peek(); - switch (jsonToken) { - case NULL: - in.nextNull(); - return null; - case NUMBER: - case STRING: - return new LazilyParsedNumber(in.nextString()); - default: - throw new JsonSyntaxException("Expecting number, got: " + jsonToken); - } - } - @Override - public void write(JsonWriter out, Number value) throws IOException { - out.value(value); - } - }; - - public static final TypeAdapterFactory NUMBER_FACTORY = newFactory(Number.class, NUMBER); - public static final TypeAdapter CHARACTER = new TypeAdapter() { @Override public Character read(JsonReader in) throws IOException { diff --git a/gson/src/test/java/com/google/gson/GsonTest.java b/gson/src/test/java/com/google/gson/GsonTest.java index eec2ec91ca..d537f7ad5b 100644 --- a/gson/src/test/java/com/google/gson/GsonTest.java +++ b/gson/src/test/java/com/google/gson/GsonTest.java @@ -44,12 +44,16 @@ public final class GsonTest extends TestCase { } }; + private static final ToNumberStrategy CUSTOM_OBJECT_TO_NUMBER_STRATEGY = ToNumberPolicy.DOUBLE; + private static final ToNumberStrategy CUSTOM_NUMBER_TO_NUMBER_STRATEGY = ToNumberPolicy.LAZILY_PARSED_NUMBER; + public void testOverridesDefaultExcluder() { Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY, new HashMap>(), true, false, true, false, true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, new ArrayList(), - new ArrayList(), new ArrayList()); + new ArrayList(), new ArrayList(), + CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY); assertEquals(CUSTOM_EXCLUDER, gson.excluder()); assertEquals(CUSTOM_FIELD_NAMING_STRATEGY, gson.fieldNamingStrategy()); @@ -62,7 +66,8 @@ public void testClonedTypeAdapterFactoryListsAreIndependent() { new HashMap>(), true, false, true, false, true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, new ArrayList(), - new ArrayList(), new ArrayList()); + new ArrayList(), new ArrayList(), + CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY); Gson clone = original.newBuilder() .registerTypeAdapter(Object.class, new TestTypeAdapter()) diff --git a/gson/src/test/java/com/google/gson/ToNumberPolicyTest.java b/gson/src/test/java/com/google/gson/ToNumberPolicyTest.java new file mode 100644 index 0000000000..d4f77f2905 --- /dev/null +++ b/gson/src/test/java/com/google/gson/ToNumberPolicyTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * 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 com.google.gson; + +import java.io.IOException; +import java.io.StringReader; +import java.math.BigDecimal; +import com.google.gson.internal.LazilyParsedNumber; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.MalformedJsonException; +import junit.framework.TestCase; + +public class ToNumberPolicyTest extends TestCase { + public void testDouble() throws IOException { + ToNumberStrategy strategy = ToNumberPolicy.DOUBLE; + assertEquals(10.1, strategy.readNumber(fromString("10.1"))); + assertEquals(3.141592653589793D, strategy.readNumber(fromString("3.141592653589793238462643383279"))); + try { + strategy.readNumber(fromString("1e400")); + fail(); + } catch (MalformedJsonException expected) { + } + } + + public void testLazilyParsedNumber() throws IOException { + ToNumberStrategy strategy = ToNumberPolicy.LAZILY_PARSED_NUMBER; + assertEquals(new LazilyParsedNumber("10.1"), strategy.readNumber(fromString("10.1"))); + assertEquals(new LazilyParsedNumber("3.141592653589793238462643383279"), strategy.readNumber(fromString("3.141592653589793238462643383279"))); + assertEquals(new LazilyParsedNumber("1e400"), strategy.readNumber(fromString("1e400"))); + } + + public void testLongOrDouble() throws IOException { + ToNumberStrategy strategy = ToNumberPolicy.LONG_OR_DOUBLE; + assertEquals(10L, strategy.readNumber(fromString("10"))); + assertEquals(10.1, strategy.readNumber(fromString("10.1"))); + assertEquals(3.141592653589793D, strategy.readNumber(fromString("3.141592653589793238462643383279"))); + try { + strategy.readNumber(fromString("1e400")); + fail(); + } catch (MalformedJsonException expected) { + } + assertEquals(Double.NaN, strategy.readNumber(fromStringLenient("NaN"))); + assertEquals(Double.POSITIVE_INFINITY, strategy.readNumber(fromStringLenient("Infinity"))); + assertEquals(Double.NEGATIVE_INFINITY, strategy.readNumber(fromStringLenient("-Infinity"))); + try { + strategy.readNumber(fromString("NaN")); + fail(); + } catch (MalformedJsonException expected) { + } + try { + strategy.readNumber(fromString("Infinity")); + fail(); + } catch (MalformedJsonException expected) { + } + try { + strategy.readNumber(fromString("-Infinity")); + fail(); + } catch (MalformedJsonException expected) { + } + } + + public void testBigDecimal() throws IOException { + ToNumberStrategy strategy = ToNumberPolicy.BIG_DECIMAL; + assertEquals(new BigDecimal("10.1"), strategy.readNumber(fromString("10.1"))); + assertEquals(new BigDecimal("3.141592653589793238462643383279"), strategy.readNumber(fromString("3.141592653589793238462643383279"))); + assertEquals(new BigDecimal("1e400"), strategy.readNumber(fromString("1e400"))); + } + + public void testNullsAreNeverExpected() throws IOException { + try { + ToNumberPolicy.DOUBLE.readNumber(fromString("null")); + fail(); + } catch (IllegalStateException expected) { + } + try { + ToNumberPolicy.LAZILY_PARSED_NUMBER.readNumber(fromString("null")); + fail(); + } catch (IllegalStateException expected) { + } + try { + ToNumberPolicy.LONG_OR_DOUBLE.readNumber(fromString("null")); + fail(); + } catch (IllegalStateException expected) { + } + try { + ToNumberPolicy.BIG_DECIMAL.readNumber(fromString("null")); + fail(); + } catch (IllegalStateException expected) { + } + } + + private static JsonReader fromString(String json) { + return new JsonReader(new StringReader(json)); + } + + private static JsonReader fromStringLenient(String json) { + JsonReader jsonReader = fromString(json); + jsonReader.setLenient(true); + return jsonReader; + } +} diff --git a/gson/src/test/java/com/google/gson/functional/ToNumberPolicyFunctionalTest.java b/gson/src/test/java/com/google/gson/functional/ToNumberPolicyFunctionalTest.java new file mode 100644 index 0000000000..07d99b812b --- /dev/null +++ b/gson/src/test/java/com/google/gson/functional/ToNumberPolicyFunctionalTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * 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 com.google.gson.functional; + +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.ToNumberPolicy; +import com.google.gson.ToNumberStrategy; +import com.google.gson.internal.LazilyParsedNumber; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import junit.framework.TestCase; + +public class ToNumberPolicyFunctionalTest extends TestCase { + public void testDefault() { + Gson gson = new Gson(); + assertEquals(null, gson.fromJson("null", Object.class)); + assertEquals(10D, gson.fromJson("10", Object.class)); + assertEquals(null, gson.fromJson("null", Number.class)); + assertEquals(new LazilyParsedNumber("10"), gson.fromJson("10", Number.class)); + } + + public void testAsDoubles() { + Gson gson = new GsonBuilder() + .setObjectToNumberStrategy(ToNumberPolicy.DOUBLE) + .setNumberToNumberStrategy(ToNumberPolicy.DOUBLE) + .create(); + assertEquals(null, gson.fromJson("null", Object.class)); + assertEquals(10.0, gson.fromJson("10", Object.class)); + assertEquals(null, gson.fromJson("null", Number.class)); + assertEquals(10.0, gson.fromJson("10", Number.class)); + } + + public void testAsLazilyParsedNumbers() { + Gson gson = new GsonBuilder() + .setObjectToNumberStrategy(ToNumberPolicy.LAZILY_PARSED_NUMBER) + .setNumberToNumberStrategy(ToNumberPolicy.LAZILY_PARSED_NUMBER) + .create(); + assertEquals(null, gson.fromJson("null", Object.class)); + assertEquals(new LazilyParsedNumber("10"), gson.fromJson("10", Object.class)); + assertEquals(null, gson.fromJson("null", Number.class)); + assertEquals(new LazilyParsedNumber("10"), gson.fromJson("10", Number.class)); + } + + public void testAsLongsOrDoubles() { + Gson gson = new GsonBuilder() + .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .create(); + assertEquals(null, gson.fromJson("null", Object.class)); + assertEquals(10L, gson.fromJson("10", Object.class)); + assertEquals(10.0, gson.fromJson("10.0", Object.class)); + assertEquals(null, gson.fromJson("null", Number.class)); + assertEquals(10L, gson.fromJson("10", Number.class)); + assertEquals(10.0, gson.fromJson("10.0", Number.class)); + } + + public void testAsBigDecimals() { + Gson gson = new GsonBuilder() + .setObjectToNumberStrategy(ToNumberPolicy.BIG_DECIMAL) + .setNumberToNumberStrategy(ToNumberPolicy.BIG_DECIMAL) + .create(); + assertEquals(null, gson.fromJson("null", Object.class)); + assertEquals(new BigDecimal("10"), gson.fromJson("10", Object.class)); + assertEquals(new BigDecimal("10.0"), gson.fromJson("10.0", Object.class)); + assertEquals(null, gson.fromJson("null", Number.class)); + assertEquals(new BigDecimal("10"), gson.fromJson("10", Number.class)); + assertEquals(new BigDecimal("10.0"), gson.fromJson("10.0", Number.class)); + assertEquals(new BigDecimal("3.141592653589793238462643383279"), gson.fromJson("3.141592653589793238462643383279", BigDecimal.class)); + assertEquals(new BigDecimal("1e400"), gson.fromJson("1e400", BigDecimal.class)); + } + + public void testAsListOfLongsOrDoubles() { + Gson gson = new GsonBuilder() + .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .create(); + List expected = new LinkedList(); + expected.add(null); + expected.add(10L); + expected.add(10.0); + Type objectCollectionType = new TypeToken>() { }.getType(); + Collection objects = gson.fromJson("[null,10,10.0]", objectCollectionType); + assertEquals(expected, objects); + Type numberCollectionType = new TypeToken>() { }.getType(); + Collection numbers = gson.fromJson("[null,10,10.0]", numberCollectionType); + assertEquals(expected, numbers); + } + + public void testCustomStrategiesCannotAffectConcreteDeclaredNumbers() { + ToNumberStrategy fail = new ToNumberStrategy() { + @Override + public Byte readNumber(JsonReader in) { + throw new UnsupportedOperationException(); + } + }; + Gson gson = new GsonBuilder() + .setObjectToNumberStrategy(fail) + .setNumberToNumberStrategy(fail) + .create(); + List numbers = gson.fromJson("[null, 10, 20, 30]", new TypeToken>() {}.getType()); + assertEquals(Arrays.asList(null, (byte) 10, (byte) 20, (byte) 30), numbers); + try { + gson.fromJson("[null, 10, 20, 30]", new TypeToken>() {}.getType()); + fail(); + } catch (UnsupportedOperationException ex) { + } + try { + gson.fromJson("[null, 10, 20, 30]", new TypeToken>() {}.getType()); + fail(); + } catch (UnsupportedOperationException ex) { + } + } +} From cd748df7122ea4260d35dfe90cfab0c079a1504d Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 11 Oct 2021 21:34:32 +0200 Subject: [PATCH 52/90] Fix LongSerializationPolicy null handling being inconsistent with Gson (#1990) Gson does not actually use the specified LongSerializationPolicy but instead uses type adapters which emulate the behavior. However, previously Gson's implementation did not match LongSerializationPolicy regarding null handling. Because it is rather unlikely that LongSerializationPolicy has been used on its own, this commit adjusts its implementation to match Gson's behavior (instead of the other way around). --- .../google/gson/LongSerializationPolicy.java | 20 +++++++++--- .../gson/LongSerializationPolicyTest.java | 32 +++++++++++++++---- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/gson/src/main/java/com/google/gson/LongSerializationPolicy.java b/gson/src/main/java/com/google/gson/LongSerializationPolicy.java index bb2b6666ea..3cc9c7241a 100644 --- a/gson/src/main/java/com/google/gson/LongSerializationPolicy.java +++ b/gson/src/main/java/com/google/gson/LongSerializationPolicy.java @@ -17,7 +17,7 @@ package com.google.gson; /** - * Defines the expected format for a {@code long} or {@code Long} type when its serialized. + * Defines the expected format for a {@code long} or {@code Long} type when it is serialized. * * @since 1.3 * @@ -26,13 +26,18 @@ */ public enum LongSerializationPolicy { /** - * This is the "default" serialization policy that will output a {@code long} object as a JSON + * This is the "default" serialization policy that will output a {@code Long} object as a JSON * number. For example, assume an object has a long field named "f" then the serialized output * would be: - * {@code {"f":123}}. + * {@code {"f":123}} + * + *

A {@code null} value is serialized as {@link JsonNull}. */ DEFAULT() { @Override public JsonElement serialize(Long value) { + if (value == null) { + return JsonNull.INSTANCE; + } return new JsonPrimitive(value); } }, @@ -40,11 +45,16 @@ public enum LongSerializationPolicy { /** * Serializes a long value as a quoted string. For example, assume an object has a long field * named "f" then the serialized output would be: - * {@code {"f":"123"}}. + * {@code {"f":"123"}} + * + *

A {@code null} value is serialized as {@link JsonNull}. */ STRING() { @Override public JsonElement serialize(Long value) { - return new JsonPrimitive(String.valueOf(value)); + if (value == null) { + return JsonNull.INSTANCE; + } + return new JsonPrimitive(value.toString()); } }; diff --git a/gson/src/test/java/com/google/gson/LongSerializationPolicyTest.java b/gson/src/test/java/com/google/gson/LongSerializationPolicyTest.java index d0a0632081..a4ad8f1423 100644 --- a/gson/src/test/java/com/google/gson/LongSerializationPolicyTest.java +++ b/gson/src/test/java/com/google/gson/LongSerializationPolicyTest.java @@ -29,21 +29,31 @@ public class LongSerializationPolicyTest extends TestCase { public void testDefaultLongSerialization() throws Exception { JsonElement element = LongSerializationPolicy.DEFAULT.serialize(1556L); assertTrue(element.isJsonPrimitive()); - + JsonPrimitive jsonPrimitive = element.getAsJsonPrimitive(); assertFalse(jsonPrimitive.isString()); assertTrue(jsonPrimitive.isNumber()); assertEquals(1556L, element.getAsLong()); } - + public void testDefaultLongSerializationIntegration() { Gson gson = new GsonBuilder() - .setLongSerializationPolicy(LongSerializationPolicy.DEFAULT) - .create(); + .setLongSerializationPolicy(LongSerializationPolicy.DEFAULT) + .create(); assertEquals("[1]", gson.toJson(new long[] { 1L }, long[].class)); assertEquals("[1]", gson.toJson(new Long[] { 1L }, Long[].class)); } + public void testDefaultLongSerializationNull() { + LongSerializationPolicy policy = LongSerializationPolicy.DEFAULT; + assertTrue(policy.serialize(null).isJsonNull()); + + Gson gson = new GsonBuilder() + .setLongSerializationPolicy(policy) + .create(); + assertEquals("null", gson.toJson(null, Long.class)); + } + public void testStringLongSerialization() throws Exception { JsonElement element = LongSerializationPolicy.STRING.serialize(1556L); assertTrue(element.isJsonPrimitive()); @@ -56,9 +66,19 @@ public void testStringLongSerialization() throws Exception { public void testStringLongSerializationIntegration() { Gson gson = new GsonBuilder() - .setLongSerializationPolicy(LongSerializationPolicy.STRING) - .create(); + .setLongSerializationPolicy(LongSerializationPolicy.STRING) + .create(); assertEquals("[\"1\"]", gson.toJson(new long[] { 1L }, long[].class)); assertEquals("[\"1\"]", gson.toJson(new Long[] { 1L }, Long[].class)); } + + public void testStringLongSerializationNull() { + LongSerializationPolicy policy = LongSerializationPolicy.STRING; + assertTrue(policy.serialize(null).isJsonNull()); + + Gson gson = new GsonBuilder() + .setLongSerializationPolicy(policy) + .create(); + assertEquals("null", gson.toJson(null, Long.class)); + } } From bda2e3d16af776e0f607d56bbab6eac22f8f2d58 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Tue, 12 Oct 2021 01:14:47 +0200 Subject: [PATCH 53/90] Improve number strategy implementation (#1987) * Fix GsonBuilder not copying number strategies from Gson * Improve ToNumberPolicy exception messages --- .../java/com/google/gson/GsonBuilder.java | 2 ++ .../java/com/google/gson/ToNumberPolicy.java | 6 ++--- .../gson/internal/bind/JsonTreeReader.java | 2 +- .../com/google/gson/ToNumberPolicyTest.java | 24 +++++++++++++++++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index 1874e7de9b..b798604ab6 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -130,6 +130,8 @@ public GsonBuilder() { this.timeStyle = gson.timeStyle; this.factories.addAll(gson.builderFactories); this.hierarchyFactories.addAll(gson.builderHierarchyFactories); + this.objectToNumberStrategy = gson.objectToNumberStrategy; + this.numberToNumberStrategy = gson.numberToNumberStrategy; } /** diff --git a/gson/src/main/java/com/google/gson/ToNumberPolicy.java b/gson/src/main/java/com/google/gson/ToNumberPolicy.java index 1c6f349dc5..e7f91c9300 100644 --- a/gson/src/main/java/com/google/gson/ToNumberPolicy.java +++ b/gson/src/main/java/com/google/gson/ToNumberPolicy.java @@ -71,11 +71,11 @@ public enum ToNumberPolicy implements ToNumberStrategy { try { Double d = Double.valueOf(value); if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) { - throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + in); + throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + "; at path " + in.getPath()); } return d; } catch (NumberFormatException doubleE) { - throw new JsonParseException("Cannot parse " + value, doubleE); + throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPath(), doubleE); } } } @@ -91,7 +91,7 @@ public enum ToNumberPolicy implements ToNumberStrategy { try { return new BigDecimal(value); } catch (NumberFormatException e) { - throw new JsonParseException("Cannot parse " + value, e); + throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPath(), e); } } } diff --git a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java index 60f4296bb0..ac6593350e 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java +++ b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java @@ -283,7 +283,7 @@ JsonElement nextJsonElement() throws IOException { } @Override public String toString() { - return getClass().getSimpleName(); + return getClass().getSimpleName() + locationString(); } public void promoteNameToValue() throws IOException { diff --git a/gson/src/test/java/com/google/gson/ToNumberPolicyTest.java b/gson/src/test/java/com/google/gson/ToNumberPolicyTest.java index d4f77f2905..db9898d47d 100644 --- a/gson/src/test/java/com/google/gson/ToNumberPolicyTest.java +++ b/gson/src/test/java/com/google/gson/ToNumberPolicyTest.java @@ -33,6 +33,12 @@ public void testDouble() throws IOException { strategy.readNumber(fromString("1e400")); fail(); } catch (MalformedJsonException expected) { + assertEquals("JSON forbids NaN and infinities: Infinity at line 1 column 6 path $", expected.getMessage()); + } + try { + strategy.readNumber(fromString("\"not-a-number\"")); + fail(); + } catch (NumberFormatException expected) { } } @@ -52,7 +58,15 @@ public void testLongOrDouble() throws IOException { strategy.readNumber(fromString("1e400")); fail(); } catch (MalformedJsonException expected) { + assertEquals("JSON forbids NaN and infinities: Infinity; at path $", expected.getMessage()); + } + try { + strategy.readNumber(fromString("\"not-a-number\"")); + fail(); + } catch (JsonParseException expected) { + assertEquals("Cannot parse not-a-number; at path $", expected.getMessage()); } + assertEquals(Double.NaN, strategy.readNumber(fromStringLenient("NaN"))); assertEquals(Double.POSITIVE_INFINITY, strategy.readNumber(fromStringLenient("Infinity"))); assertEquals(Double.NEGATIVE_INFINITY, strategy.readNumber(fromStringLenient("-Infinity"))); @@ -60,16 +74,19 @@ public void testLongOrDouble() throws IOException { strategy.readNumber(fromString("NaN")); fail(); } catch (MalformedJsonException expected) { + assertEquals("Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $", expected.getMessage()); } try { strategy.readNumber(fromString("Infinity")); fail(); } catch (MalformedJsonException expected) { + assertEquals("Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $", expected.getMessage()); } try { strategy.readNumber(fromString("-Infinity")); fail(); } catch (MalformedJsonException expected) { + assertEquals("Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $", expected.getMessage()); } } @@ -78,6 +95,13 @@ public void testBigDecimal() throws IOException { assertEquals(new BigDecimal("10.1"), strategy.readNumber(fromString("10.1"))); assertEquals(new BigDecimal("3.141592653589793238462643383279"), strategy.readNumber(fromString("3.141592653589793238462643383279"))); assertEquals(new BigDecimal("1e400"), strategy.readNumber(fromString("1e400"))); + + try { + strategy.readNumber(fromString("\"not-a-number\"")); + fail(); + } catch (JsonParseException expected) { + assertEquals("Cannot parse not-a-number; at path $", expected.getMessage()); + } } public void testNullsAreNeverExpected() throws IOException { From e6fae590cf2a758c47cd5a17f9bf3780ce62c986 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Wed, 13 Oct 2021 19:14:57 +0200 Subject: [PATCH 54/90] Prevent Java deserialization of internal classes (#1991) Adversaries might be able to forge data which can be abused for DoS attacks. These classes are already writing a replacement JDK object during serialization for a long time, so this change should not cause any issues. --- .../gson/internal/LazilyParsedNumber.java | 8 +++++++ .../gson/internal/LinkedHashTreeMap.java | 8 +++++++ .../google/gson/internal/LinkedTreeMap.java | 8 +++++++ .../gson/internal/LazilyParsedNumberTest.java | 18 ++++++++++++++++ .../gson/internal/LinkedHashTreeMapTest.java | 21 +++++++++++++++++++ .../gson/internal/LinkedTreeMapTest.java | 20 ++++++++++++++++++ 6 files changed, 83 insertions(+) diff --git a/gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java b/gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java index 3669af7b58..6138dfff38 100644 --- a/gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java +++ b/gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java @@ -15,6 +15,9 @@ */ package com.google.gson.internal; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.ObjectStreamException; import java.math.BigDecimal; @@ -77,6 +80,11 @@ private Object writeReplace() throws ObjectStreamException { return new BigDecimal(value); } + private void readObject(ObjectInputStream in) throws IOException { + // Don't permit directly deserializing this class; writeReplace() should have written a replacement + throw new InvalidObjectException("Deserialization is unsupported"); + } + @Override public int hashCode() { return value.hashCode(); diff --git a/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java b/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java index b2707c50da..0cade0d1f8 100644 --- a/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java +++ b/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java @@ -17,6 +17,9 @@ package com.google.gson.internal; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.AbstractMap; @@ -861,4 +864,9 @@ public K next() { private Object writeReplace() throws ObjectStreamException { return new LinkedHashMap(this); } + + private void readObject(ObjectInputStream in) throws IOException { + // Don't permit directly deserializing this class; writeReplace() should have written a replacement + throw new InvalidObjectException("Deserialization is unsupported"); + } } diff --git a/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java b/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java index 80462742e3..aaa8ce0918 100644 --- a/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java +++ b/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java @@ -17,6 +17,9 @@ package com.google.gson.internal; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.AbstractMap; @@ -627,4 +630,9 @@ public K next() { private Object writeReplace() throws ObjectStreamException { return new LinkedHashMap(this); } + + private void readObject(ObjectInputStream in) throws IOException { + // Don't permit directly deserializing this class; writeReplace() should have written a replacement + throw new InvalidObjectException("Deserialization is unsupported"); + } } diff --git a/gson/src/test/java/com/google/gson/internal/LazilyParsedNumberTest.java b/gson/src/test/java/com/google/gson/internal/LazilyParsedNumberTest.java index f108fa0de8..75e77bb55f 100644 --- a/gson/src/test/java/com/google/gson/internal/LazilyParsedNumberTest.java +++ b/gson/src/test/java/com/google/gson/internal/LazilyParsedNumberTest.java @@ -15,6 +15,13 @@ */ package com.google.gson.internal; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.math.BigDecimal; + import junit.framework.TestCase; public class LazilyParsedNumberTest extends TestCase { @@ -29,4 +36,15 @@ public void testEquals() { LazilyParsedNumber n1Another = new LazilyParsedNumber("1"); assertTrue(n1.equals(n1Another)); } + + public void testJavaSerialization() throws IOException, ClassNotFoundException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream objOut = new ObjectOutputStream(out); + objOut.writeObject(new LazilyParsedNumber("123")); + objOut.close(); + + ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray())); + Number deserialized = (Number) objIn.readObject(); + assertEquals(new BigDecimal("123"), deserialized); + } } diff --git a/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java b/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java index df595b796b..65864f0c97 100644 --- a/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java +++ b/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java @@ -20,8 +20,15 @@ import com.google.gson.internal.LinkedHashTreeMap.AvlBuilder; import com.google.gson.internal.LinkedHashTreeMap.AvlIterator; import com.google.gson.internal.LinkedHashTreeMap.Node; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Random; @@ -224,6 +231,20 @@ public void testDoubleCapacityAllNodesOnLeft() { } } + public void testJavaSerialization() throws IOException, ClassNotFoundException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream objOut = new ObjectOutputStream(out); + Map map = new LinkedHashTreeMap(); + map.put("a", 1); + objOut.writeObject(map); + objOut.close(); + + ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray())); + @SuppressWarnings("unchecked") + Map deserialized = (Map) objIn.readObject(); + assertEquals(Collections.singletonMap("a", 1), deserialized); + } + private static final Node head = new Node(); private Node node(String value) { diff --git a/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java b/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java index 058de3367f..68220cf631 100644 --- a/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java +++ b/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java @@ -16,8 +16,14 @@ package com.google.gson.internal; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Random; @@ -140,6 +146,20 @@ public void testEqualsAndHashCode() throws Exception { MoreAsserts.assertEqualsAndHashCode(map1, map2); } + public void testJavaSerialization() throws IOException, ClassNotFoundException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream objOut = new ObjectOutputStream(out); + Map map = new LinkedTreeMap(); + map.put("a", 1); + objOut.writeObject(map); + objOut.close(); + + ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray())); + @SuppressWarnings("unchecked") + Map deserialized = (Map) objIn.readObject(); + assertEquals(Collections.singletonMap("a", 1), deserialized); + } + @SafeVarargs private void assertIterationOrder(Iterable actual, T... expected) { ArrayList actualList = new ArrayList(); From c54caf308c3f7d4a6088cf3085c2caa9617e0458 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 25 Oct 2021 20:28:16 +0200 Subject: [PATCH 55/90] Deprecate `Gson.excluder()` exposing internal `Excluder` class (#1986) --- gson/src/main/java/com/google/gson/Gson.java | 22 +++++++++++++++++++ .../test/java/com/google/gson/GsonTest.java | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 1511bbb178..ef7b875195 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -299,18 +299,40 @@ public GsonBuilder newBuilder() { return new GsonBuilder(this); } + /** + * @deprecated This method by accident exposes an internal Gson class; it might be removed in a + * future version. + */ + @Deprecated public Excluder excluder() { return excluder; } + /** + * Returns the field naming strategy used by this Gson instance. + * + * @see GsonBuilder#setFieldNamingStrategy(FieldNamingStrategy) + */ public FieldNamingStrategy fieldNamingStrategy() { return fieldNamingStrategy; } + /** + * Returns whether this Gson instance is serializing JSON object properties with + * {@code null} values, or just omits them. + * + * @see GsonBuilder#serializeNulls() + */ public boolean serializeNulls() { return serializeNulls; } + /** + * Returns whether this Gson instance produces JSON output which is + * HTML-safe, that means all HTML characters are escaped. + * + * @see GsonBuilder#disableHtmlEscaping() + */ public boolean htmlSafe() { return htmlSafe; } diff --git a/gson/src/test/java/com/google/gson/GsonTest.java b/gson/src/test/java/com/google/gson/GsonTest.java index d537f7ad5b..c6cc4d5426 100644 --- a/gson/src/test/java/com/google/gson/GsonTest.java +++ b/gson/src/test/java/com/google/gson/GsonTest.java @@ -55,7 +55,7 @@ public void testOverridesDefaultExcluder() { new ArrayList(), new ArrayList(), CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY); - assertEquals(CUSTOM_EXCLUDER, gson.excluder()); + assertEquals(CUSTOM_EXCLUDER, gson.excluder); assertEquals(CUSTOM_FIELD_NAMING_STRATEGY, gson.fieldNamingStrategy()); assertEquals(true, gson.serializeNulls()); assertEquals(false, gson.htmlSafe()); From ca1df7f7e09f6b1a763882029dd7057f475b31de Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 25 Oct 2021 20:32:10 +0200 Subject: [PATCH 56/90] #1981: Optional OSGi bundle's dependency on sun.misc package (#1993) * #1981: Avoid OSGi bundle's dependency on sun.misc package * Specify optional dependency on sun.misc.Unsafe * Adjusting the test to sun.misc import being optional * Using Collections.list and for loop * Let the fail message include name of package Co-authored-by: Marcono1234 * Closing the input stream * Dedicated assertSubstring method Co-authored-by: Marcono1234 --- gson/bnd.bnd | 4 ++ gson/src/main/java/module-info.java | 3 + .../com/google/gson/regression/OSGiTest.java | 70 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 gson/src/test/java/com/google/gson/regression/OSGiTest.java diff --git a/gson/bnd.bnd b/gson/bnd.bnd index 57a8657fd8..07746f0a48 100644 --- a/gson/bnd.bnd +++ b/gson/bnd.bnd @@ -6,6 +6,10 @@ Bundle-ContactAddress: ${project.parent.url} Bundle-RequiredExecutionEnvironment: JavaSE-1.6, JavaSE-1.7, JavaSE-1.8 Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.6))" +# Optional dependency for JDK's sun.misc.Unsafe +# https://bnd.bndtools.org/chapters/920-faq.html#remove-unwanted-imports- +Import-Package: sun.misc;resolution:=optional, * + -removeheaders: Private-Package -exportcontents:\ diff --git a/gson/src/main/java/module-info.java b/gson/src/main/java/module-info.java index 38c26e569c..0134c9dc19 100644 --- a/gson/src/main/java/module-info.java +++ b/gson/src/main/java/module-info.java @@ -10,4 +10,7 @@ // Optional dependency on java.sql requires static java.sql; + + // Optional dependency on jdk.unsupported for JDK's sun.misc.Unsafe + requires static jdk.unsupported; } diff --git a/gson/src/test/java/com/google/gson/regression/OSGiTest.java b/gson/src/test/java/com/google/gson/regression/OSGiTest.java new file mode 100644 index 0000000000..64a7e371ab --- /dev/null +++ b/gson/src/test/java/com/google/gson/regression/OSGiTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 Google Inc. + * + * 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 com.google.gson.regression; + +import java.io.InputStream; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.jar.Manifest; + +import junit.framework.TestCase; + +public class OSGiTest extends TestCase { + public void testComGoogleGsonAnnotationsPackage() throws Exception { + Manifest mf = findManifest("com.google.gson"); + String importPkg = mf.getMainAttributes().getValue("Import-Package"); + assertNotNull("Import-Package statement is there", importPkg); + assertSubstring("There should be com.google.gson.annotations dependency", importPkg, "com.google.gson.annotations"); + } + + public void testSunMiscImportPackage() throws Exception { + Manifest mf = findManifest("com.google.gson"); + String importPkg = mf.getMainAttributes().getValue("Import-Package"); + assertNotNull("Import-Package statement is there", importPkg); + for (String dep : importPkg.split(",")) { + if (dep.contains("sun.misc")) { + assertSubstring("sun.misc import is optional", dep, "resolution:=optional"); + return; + } + } + fail("There should be sun.misc dependency, but was: " + importPkg); + } + + private Manifest findManifest(String pkg) throws IOException { + List urls = new ArrayList(); + for (URL u : Collections.list(getClass().getClassLoader().getResources("META-INF/MANIFEST.MF"))) { + InputStream is = u.openStream(); + Manifest mf = new Manifest(is); + is.close(); + if (pkg.equals(mf.getMainAttributes().getValue("Bundle-SymbolicName"))) { + return mf; + } + urls.add(u); + } + fail("Cannot find " + pkg + " OSGi bundle manifest among: " + urls); + return null; + } + + private static void assertSubstring(String msg, String wholeText, String subString) { + if (wholeText.contains(subString)) { + return; + } + fail(msg + ". Expecting " + subString + " but was: " + wholeText); + } +} From ba96d53bad35f7466073f14cb3d89d09383e1a2d Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 25 Oct 2021 21:14:41 +0200 Subject: [PATCH 57/90] Fix missing bounds checks for JsonTreeReader.getPath() (#2001) There are situations where the stack of JsonTreeReader contains a JsonArray or JsonObject without a subsequent Iterator, for example after calling peek() or nextName(). When JsonTreeReader.getPath() is called afterwards it therefore must not assume that a JsonArray or JsonObject is always followed by an Iterator. The only reason why this never caused an ArrayIndexOutOfBoundsException in the past is because the stack has an even default size (32) so it would just have read the next `null`. However, if the stack had for example the default size 31, a user created a JsonTreeReader for 16 JSON arrays nested inside each other, then called 15 times beginArray(), followed by peek() and getPath() the exception would occur. --- .../java/com/google/gson/internal/bind/JsonTreeReader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java index ac6593350e..0954fb332b 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java +++ b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java @@ -308,11 +308,11 @@ private void push(Object newTop) { StringBuilder result = new StringBuilder().append('$'); for (int i = 0; i < stackSize; i++) { if (stack[i] instanceof JsonArray) { - if (stack[++i] instanceof Iterator) { + if (++i < stackSize && stack[i] instanceof Iterator) { result.append('[').append(pathIndices[i]).append(']'); } } else if (stack[i] instanceof JsonObject) { - if (stack[++i] instanceof Iterator) { + if (++i < stackSize && stack[i] instanceof Iterator) { result.append('.'); if (pathNames[i] != null) { result.append(pathNames[i]); From 6a368d89da37917be7714c3072b8378f4120110a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Fri, 29 Oct 2021 12:58:54 -0700 Subject: [PATCH 58/90] [maven-release-plugin] prepare release gson-parent-2.8.9 --- extras/pom.xml | 8 +++----- gson/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/extras/pom.xml b/extras/pom.xml index 91da708bae..57b4f690b8 100644 --- a/extras/pom.xml +++ b/extras/pom.xml @@ -1,16 +1,14 @@ - + 4.0.0 com.google.code.gson gson-parent - 2.8.9-SNAPSHOT + 2.8.9 gson-extras jar - 1.0-SNAPSHOT + 2.8.9 2008 Gson Extras Google Gson grab bag of utilities, type adapters, etc. diff --git a/gson/pom.xml b/gson/pom.xml index 6d1a5eb001..2b7e0c8394 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -4,7 +4,7 @@ com.google.code.gson gson-parent - 2.8.9-SNAPSHOT + 2.8.9 gson diff --git a/pom.xml b/pom.xml index b093161a0e..807d098724 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.google.code.gson gson-parent - 2.8.9-SNAPSHOT + 2.8.9 pom Gson Parent @@ -32,7 +32,7 @@ https://github.com/google/gson/ scm:git:https://github.com/google/gson.git scm:git:git@github.com:google/gson.git - HEAD + gson-parent-2.8.9 From 128586847bc5669a4a10fd29ac3ead92b709352a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Fri, 29 Oct 2021 12:58:56 -0700 Subject: [PATCH 59/90] [maven-release-plugin] prepare for next development iteration --- extras/pom.xml | 4 ++-- gson/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extras/pom.xml b/extras/pom.xml index 57b4f690b8..bda0e646d1 100644 --- a/extras/pom.xml +++ b/extras/pom.xml @@ -3,12 +3,12 @@ com.google.code.gson gson-parent - 2.8.9 + 2.9-SNAPSHOT gson-extras jar - 2.8.9 + 2.9-SNAPSHOT 2008 Gson Extras Google Gson grab bag of utilities, type adapters, etc. diff --git a/gson/pom.xml b/gson/pom.xml index 2b7e0c8394..672ef431b1 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -4,7 +4,7 @@ com.google.code.gson gson-parent - 2.8.9 + 2.9-SNAPSHOT gson diff --git a/pom.xml b/pom.xml index 807d098724..735f86ad5e 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.google.code.gson gson-parent - 2.8.9 + 2.9-SNAPSHOT pom Gson Parent @@ -32,7 +32,7 @@ https://github.com/google/gson/ scm:git:https://github.com/google/gson.git scm:git:git@github.com:google/gson.git - gson-parent-2.8.9 + HEAD From 031db9d4737df018e93c76b75611fd3c8589034f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Fri, 29 Oct 2021 13:41:12 -0700 Subject: [PATCH 60/90] Update CHANGELOG.md for 2.8.9. (#2005) * Update CHANGELOG.md for 2.8.9. * Adjust snapshot version. Gson versions have three numbers. --- CHANGELOG.md | 14 ++++++++++++++ extras/pom.xml | 4 ++-- gson/pom.xml | 2 +- pom.xml | 2 +- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f58bb676e..0ed7837c4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ Change Log ========== +## Version 2.8.9 + +* Make OSGi bundle's dependency on `sun.misc` optional (#1993). +* Deprecate `Gson.excluder()` exposing internal `Excluder` class (#1986). +* Prevent Java deserialization of internal classes (#1991). +* Improve number strategy implementation (#1987). +* Fix LongSerializationPolicy null handling being inconsistent with Gson (#1990). +* Support arbitrary Number implementation for Object and Number deserialization (#1290). +* Bump proguard-maven-plugin from 2.4.0 to 2.5.1 (#1980). +* Don't exclude static local classes (#1969). +* Fix `RuntimeTypeAdapterFactory` depending on internal `Streams` class (#1959). +* Improve Maven build (#1964). +* Make dependency on `java.sql` optional (#1707). + ## Version 2.8.8 * Fixed issue with recursive types (#1390). diff --git a/extras/pom.xml b/extras/pom.xml index bda0e646d1..2cef36efd8 100644 --- a/extras/pom.xml +++ b/extras/pom.xml @@ -3,12 +3,12 @@ com.google.code.gson gson-parent - 2.9-SNAPSHOT + 2.9.0-SNAPSHOT gson-extras jar - 2.9-SNAPSHOT + 2.9.0-SNAPSHOT 2008 Gson Extras Google Gson grab bag of utilities, type adapters, etc. diff --git a/gson/pom.xml b/gson/pom.xml index 672ef431b1..d9d37baa33 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -4,7 +4,7 @@ com.google.code.gson gson-parent - 2.9-SNAPSHOT + 2.9.0-SNAPSHOT gson diff --git a/pom.xml b/pom.xml index 735f86ad5e..d0d45be9a1 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.google.code.gson gson-parent - 2.9-SNAPSHOT + 2.9.0-SNAPSHOT pom Gson Parent From 121bcede9663d890867179e3c700afcee25f93c0 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 31 Oct 2021 14:52:51 +0100 Subject: [PATCH 61/90] Update project version in README (#2006) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a1dd0492ae..dccace4860 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ There are a few open-source projects that can convert Java objects to JSON. Howe Gradle: ```gradle dependencies { - implementation 'com.google.code.gson:gson:2.8.8' + implementation 'com.google.code.gson:gson:2.8.9' } ``` @@ -26,7 +26,7 @@ Maven: com.google.code.gson gson - 2.8.8 + 2.8.9 ``` From 466ca729167662b72beb0b952a7819b71547eaec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Sun, 31 Oct 2021 06:57:33 -0700 Subject: [PATCH 62/90] Update version number in UserGuide.md and proto/pom.xml. --- UserGuide.md | 4 ++-- proto/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/UserGuide.md b/UserGuide.md index c5d4710b9e..331ef27663 100644 --- a/UserGuide.md +++ b/UserGuide.md @@ -74,7 +74,7 @@ The Gson instance does not maintain any state while invoking Json operations. So ## Using Gson with Gradle/Android ``` dependencies { - implementation 'com.google.code.gson:gson:2.8.8' + implementation 'com.google.code.gson:gson:2.8.9' } ``` ## Using Gson with Maven @@ -86,7 +86,7 @@ To use Gson with Maven2/3, you can use the Gson version available in Maven Centr com.google.code.gson gson - 2.8.8 + 2.8.9 compile diff --git a/proto/pom.xml b/proto/pom.xml index 14fd44ce70..afe55a1cf4 100644 --- a/proto/pom.xml +++ b/proto/pom.xml @@ -55,7 +55,7 @@ com.google.code.gson gson - 2.8.8 + 2.8.9 compile From b3188c113205bb41a980b09917b7f6b242cd32fc Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 1 Nov 2021 23:05:04 +0100 Subject: [PATCH 63/90] Fix `FieldNamingPolicy.upperCaseFirstLetter` uppercasing non-letter (#2004) --- .../com/google/gson/FieldNamingPolicy.java | 42 +++--- .../google/gson/FieldNamingPolicyTest.java | 130 ++++++++++++++++++ 2 files changed, 153 insertions(+), 19 deletions(-) create mode 100644 gson/src/test/java/com/google/gson/FieldNamingPolicyTest.java diff --git a/gson/src/main/java/com/google/gson/FieldNamingPolicy.java b/gson/src/main/java/com/google/gson/FieldNamingPolicy.java index ddb9a93d62..16e7124f45 100644 --- a/gson/src/main/java/com/google/gson/FieldNamingPolicy.java +++ b/gson/src/main/java/com/google/gson/FieldNamingPolicy.java @@ -71,7 +71,7 @@ public enum FieldNamingPolicy implements FieldNamingStrategy { */ UPPER_CAMEL_CASE_WITH_SPACES() { @Override public String translateName(Field f) { - return upperCaseFirstLetter(separateCamelCase(f.getName(), " ")); + return upperCaseFirstLetter(separateCamelCase(f.getName(), ' ')); } }, @@ -89,7 +89,7 @@ public enum FieldNamingPolicy implements FieldNamingStrategy { */ LOWER_CASE_WITH_UNDERSCORES() { @Override public String translateName(Field f) { - return separateCamelCase(f.getName(), "_").toLowerCase(Locale.ENGLISH); + return separateCamelCase(f.getName(), '_').toLowerCase(Locale.ENGLISH); } }, @@ -112,7 +112,7 @@ public enum FieldNamingPolicy implements FieldNamingStrategy { */ LOWER_CASE_WITH_DASHES() { @Override public String translateName(Field f) { - return separateCamelCase(f.getName(), "-").toLowerCase(Locale.ENGLISH); + return separateCamelCase(f.getName(), '-').toLowerCase(Locale.ENGLISH); } }, @@ -135,15 +135,15 @@ public enum FieldNamingPolicy implements FieldNamingStrategy { */ LOWER_CASE_WITH_DOTS() { @Override public String translateName(Field f) { - return separateCamelCase(f.getName(), ".").toLowerCase(Locale.ENGLISH); + return separateCamelCase(f.getName(), '.').toLowerCase(Locale.ENGLISH); } }; /** * Converts the field name that uses camel-case define word separation into - * separate words that are separated by the provided {@code separatorString}. + * separate words that are separated by the provided {@code separator}. */ - static String separateCamelCase(String name, String separator) { + static String separateCamelCase(String name, char separator) { StringBuilder translation = new StringBuilder(); for (int i = 0, length = name.length(); i < length; i++) { char character = name.charAt(i); @@ -158,21 +158,25 @@ static String separateCamelCase(String name, String separator) { /** * Ensures the JSON field names begins with an upper case letter. */ - static String upperCaseFirstLetter(String name) { - int firstLetterIndex = 0; - int limit = name.length() - 1; - for(; !Character.isLetter(name.charAt(firstLetterIndex)) && firstLetterIndex < limit; ++firstLetterIndex); + static String upperCaseFirstLetter(String s) { + int length = s.length(); + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + if (Character.isLetter(c)) { + if (Character.isUpperCase(c)) { + return s; + } - char firstLetter = name.charAt(firstLetterIndex); - if(Character.isUpperCase(firstLetter)) { //The letter is already uppercased, return the original - return name; - } - - char uppercased = Character.toUpperCase(firstLetter); - if(firstLetterIndex == 0) { //First character in the string is the first letter, saves 1 substring - return uppercased + name.substring(1); + char uppercased = Character.toUpperCase(c); + // For leading letter only need one substring + if (i == 0) { + return uppercased + s.substring(1); + } else { + return s.substring(0, i) + uppercased + s.substring(i + 1); + } + } } - return name.substring(0, firstLetterIndex) + uppercased + name.substring(firstLetterIndex + 1); + return s; } } diff --git a/gson/src/test/java/com/google/gson/FieldNamingPolicyTest.java b/gson/src/test/java/com/google/gson/FieldNamingPolicyTest.java new file mode 100644 index 0000000000..a62bae3aad --- /dev/null +++ b/gson/src/test/java/com/google/gson/FieldNamingPolicyTest.java @@ -0,0 +1,130 @@ +package com.google.gson; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import java.lang.reflect.Field; +import java.util.Locale; +import org.junit.Test; +import com.google.gson.functional.FieldNamingTest; + +/** + * Performs tests directly against {@link FieldNamingPolicy}; for integration tests + * see {@link FieldNamingTest}. + */ +public class FieldNamingPolicyTest { + @Test + public void testSeparateCamelCase() { + // Map from original -> expected + String[][] argumentPairs = { + { "a", "a" }, + { "ab", "ab" }, + { "Ab", "Ab" }, + { "aB", "a_B" }, + { "AB", "A_B" }, + { "A_B", "A__B" }, + { "firstSecondThird", "first_Second_Third" }, + { "__", "__" }, + { "_123", "_123" } + }; + + for (String[] pair : argumentPairs) { + assertEquals(pair[1], FieldNamingPolicy.separateCamelCase(pair[0], '_')); + } + } + + @Test + public void testUpperCaseFirstLetter() { + // Map from original -> expected + String[][] argumentPairs = { + { "a", "A" }, + { "ab", "Ab" }, + { "AB", "AB" }, + { "_a", "_A" }, + { "_ab", "_Ab" }, + { "__", "__" }, + { "_1", "_1" }, + // Not a letter, but has uppercase variant (should not be uppercased) + // See https://github.com/google/gson/issues/1965 + { "\u2170", "\u2170" }, + { "_\u2170", "_\u2170" }, + { "\u2170a", "\u2170A" }, + }; + + for (String[] pair : argumentPairs) { + assertEquals(pair[1], FieldNamingPolicy.upperCaseFirstLetter(pair[0])); + } + } + + /** + * Upper casing policies should be unaffected by default Locale. + */ + @Test + public void testUpperCasingLocaleIndependent() throws Exception { + class Dummy { + @SuppressWarnings("unused") + int i; + } + + FieldNamingPolicy[] policies = { + FieldNamingPolicy.UPPER_CAMEL_CASE, + FieldNamingPolicy.UPPER_CAMEL_CASE_WITH_SPACES + }; + + Field field = Dummy.class.getDeclaredField("i"); + String name = field.getName(); + String expected = name.toUpperCase(Locale.ROOT); + + Locale oldLocale = Locale.getDefault(); + // Set Turkish as Locale which has special case conversion rules + Locale.setDefault(new Locale("tr")); + + try { + // Verify that default Locale has different case conversion rules + assertNotEquals("Test setup is broken", expected, name.toUpperCase()); + + for (FieldNamingPolicy policy : policies) { + // Should ignore default Locale + assertEquals("Unexpected conversion for " + policy, expected, policy.translateName(field)); + } + } finally { + Locale.setDefault(oldLocale); + } + } + + /** + * Lower casing policies should be unaffected by default Locale. + */ + @Test + public void testLowerCasingLocaleIndependent() throws Exception { + class Dummy { + @SuppressWarnings("unused") + int I; + } + + FieldNamingPolicy[] policies = { + FieldNamingPolicy.LOWER_CASE_WITH_DASHES, + FieldNamingPolicy.LOWER_CASE_WITH_DOTS, + FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES, + }; + + Field field = Dummy.class.getDeclaredField("I"); + String name = field.getName(); + String expected = name.toLowerCase(Locale.ROOT); + + Locale oldLocale = Locale.getDefault(); + // Set Turkish as Locale which has special case conversion rules + Locale.setDefault(new Locale("tr")); + + try { + // Verify that default Locale has different case conversion rules + assertNotEquals("Test setup is broken", expected, name.toLowerCase()); + + for (FieldNamingPolicy policy : policies) { + // Should ignore default Locale + assertEquals("Unexpected conversion for " + policy, expected, policy.translateName(field)); + } + } finally { + Locale.setDefault(oldLocale); + } + } +} From b4dab86b105c85e6b7d7106c9ff11e3e923e3485 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 1 Nov 2021 23:08:04 +0100 Subject: [PATCH 64/90] Make default adapters stricter; improve exception messages (#2000) * Make default adapters stricter; improve exception messages * Reduce scope of synchronized blocks * Improve JsonReader.getPath / getPreviousPath Javadoc --- .../java/com/google/gson/ToNumberPolicy.java | 6 +- .../gson/internal/bind/DateTypeAdapter.java | 30 ++++---- .../internal/bind/DefaultDateTypeAdapter.java | 16 +++-- .../gson/internal/bind/JsonTreeReader.java | 19 ++++- .../gson/internal/bind/NumberTypeAdapter.java | 2 +- .../gson/internal/bind/TypeAdapters.java | 68 ++++++++++++------ .../gson/internal/sql/SqlDateTypeAdapter.java | 25 +++++-- .../gson/internal/sql/SqlTimeTypeAdapter.java | 23 +++++-- .../com/google/gson/stream/JsonReader.java | 62 +++++++++++++---- .../functional/DefaultTypeAdaptersTest.java | 16 ++++- .../google/gson/functional/PrimitiveTest.java | 69 +++++++++++++++++-- .../gson/stream/JsonReaderPathTest.java | 68 ++++++++++++++++++ 12 files changed, 327 insertions(+), 77 deletions(-) diff --git a/gson/src/main/java/com/google/gson/ToNumberPolicy.java b/gson/src/main/java/com/google/gson/ToNumberPolicy.java index e7f91c9300..bd70213b64 100644 --- a/gson/src/main/java/com/google/gson/ToNumberPolicy.java +++ b/gson/src/main/java/com/google/gson/ToNumberPolicy.java @@ -71,11 +71,11 @@ public enum ToNumberPolicy implements ToNumberStrategy { try { Double d = Double.valueOf(value); if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) { - throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + "; at path " + in.getPath()); + throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + "; at path " + in.getPreviousPath()); } return d; } catch (NumberFormatException doubleE) { - throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPath(), doubleE); + throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), doubleE); } } } @@ -91,7 +91,7 @@ public enum ToNumberPolicy implements ToNumberStrategy { try { return new BigDecimal(value); } catch (NumberFormatException e) { - throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPath(), e); + throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), e); } } } diff --git a/gson/src/main/java/com/google/gson/internal/bind/DateTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/DateTypeAdapter.java index 6e849690ea..c5b16a81d1 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/DateTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/DateTypeAdapter.java @@ -72,30 +72,36 @@ public DateTypeAdapter() { in.nextNull(); return null; } - return deserializeToDate(in.nextString()); + return deserializeToDate(in); } - private synchronized Date deserializeToDate(String json) { - for (DateFormat dateFormat : dateFormats) { - try { - return dateFormat.parse(json); - } catch (ParseException ignored) {} + private Date deserializeToDate(JsonReader in) throws IOException { + String s = in.nextString(); + synchronized (dateFormats) { + for (DateFormat dateFormat : dateFormats) { + try { + return dateFormat.parse(s); + } catch (ParseException ignored) {} + } } try { - return ISO8601Utils.parse(json, new ParsePosition(0)); + return ISO8601Utils.parse(s, new ParsePosition(0)); } catch (ParseException e) { - throw new JsonSyntaxException(json, e); + throw new JsonSyntaxException("Failed parsing '" + s + "' as Date; at path " + in.getPreviousPath(), e); } } - @Override public synchronized void write(JsonWriter out, Date value) throws IOException { + @Override public void write(JsonWriter out, Date value) throws IOException { if (value == null) { out.nullValue(); return; } - String dateFormatAsString = dateFormats.get(0).format(value); + + DateFormat dateFormat = dateFormats.get(0); + String dateFormatAsString; + synchronized (dateFormats) { + dateFormatAsString = dateFormat.format(value); + } out.value(dateFormatAsString); } - - } diff --git a/gson/src/main/java/com/google/gson/internal/bind/DefaultDateTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/DefaultDateTypeAdapter.java index f56faee0f9..f5ee4e2137 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/DefaultDateTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/DefaultDateTypeAdapter.java @@ -130,10 +130,13 @@ public void write(JsonWriter out, Date value) throws IOException { out.nullValue(); return; } - synchronized(dateFormats) { - String dateFormatAsString = dateFormats.get(0).format(value); - out.value(dateFormatAsString); + + DateFormat dateFormat = dateFormats.get(0); + String dateFormatAsString; + synchronized (dateFormats) { + dateFormatAsString = dateFormat.format(value); } + out.value(dateFormatAsString); } @Override @@ -142,11 +145,12 @@ public T read(JsonReader in) throws IOException { in.nextNull(); return null; } - Date date = deserializeToDate(in.nextString()); + Date date = deserializeToDate(in); return dateType.deserialize(date); } - private Date deserializeToDate(String s) { + private Date deserializeToDate(JsonReader in) throws IOException { + String s = in.nextString(); synchronized (dateFormats) { for (DateFormat dateFormat : dateFormats) { try { @@ -158,7 +162,7 @@ private Date deserializeToDate(String s) { try { return ISO8601Utils.parse(s, new ParsePosition(0)); } catch (ParseException e) { - throw new JsonSyntaxException(s, e); + throw new JsonSyntaxException("Failed parsing '" + s + "' as Date; at path " + in.getPreviousPath(), e); } } diff --git a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java index 0954fb332b..f8238bc28b 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java +++ b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java @@ -304,12 +304,19 @@ private void push(Object newTop) { stack[stackSize++] = newTop; } - @Override public String getPath() { + private String getPath(boolean usePreviousPath) { StringBuilder result = new StringBuilder().append('$'); for (int i = 0; i < stackSize; i++) { if (stack[i] instanceof JsonArray) { if (++i < stackSize && stack[i] instanceof Iterator) { - result.append('[').append(pathIndices[i]).append(']'); + int pathIndex = pathIndices[i]; + // If index is last path element it points to next array element; have to decrement + // `- 1` covers case where iterator for next element is on stack + // `- 2` covers case where peek() already pushed next element onto stack + if (usePreviousPath && pathIndex > 0 && (i == stackSize - 1 || i == stackSize - 2)) { + pathIndex--; + } + result.append('[').append(pathIndex).append(']'); } } else if (stack[i] instanceof JsonObject) { if (++i < stackSize && stack[i] instanceof Iterator) { @@ -323,6 +330,14 @@ private void push(Object newTop) { return result.toString(); } + @Override public String getPreviousPath() { + return getPath(true); + } + + @Override public String getPath() { + return getPath(false); + } + private String locationString() { return " at path " + getPath(); } diff --git a/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java index f5efff2825..5aaeae3261 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java @@ -72,7 +72,7 @@ public static TypeAdapterFactory getFactory(ToNumberStrategy toNumberStrategy) { case STRING: return toNumberStrategy.readNumber(in); default: - throw new JsonSyntaxException("Expecting number, got: " + jsonToken); + throw new JsonSyntaxException("Expecting number, got: " + jsonToken + "; at path " + in.getPath()); } } diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index cd5ba2e395..1b6b0118b6 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -92,22 +92,21 @@ public Class read(JsonReader in) throws IOException { boolean set; switch (tokenType) { case NUMBER: - set = in.nextInt() != 0; + case STRING: + int intValue = in.nextInt(); + if (intValue == 0) { + set = false; + } else if (intValue == 1) { + set = true; + } else { + throw new JsonSyntaxException("Invalid bitset value " + intValue + ", expected 0 or 1; at path " + in.getPreviousPath()); + } break; case BOOLEAN: set = in.nextBoolean(); break; - case STRING: - String stringValue = in.nextString(); - try { - set = Integer.parseInt(stringValue) != 0; - } catch (NumberFormatException e) { - throw new JsonSyntaxException( - "Error: Expecting: bitset number value (1, 0), Found: " + stringValue); - } - break; default: - throw new JsonSyntaxException("Invalid bitset value type: " + tokenType); + throw new JsonSyntaxException("Invalid bitset value type: " + tokenType + "; at path " + in.getPath()); } if (set) { bitset.set(i); @@ -178,12 +177,18 @@ public Number read(JsonReader in) throws IOException { in.nextNull(); return null; } + + int intValue; try { - int intValue = in.nextInt(); - return (byte) intValue; + intValue = in.nextInt(); } catch (NumberFormatException e) { throw new JsonSyntaxException(e); } + // Allow up to 255 to support unsigned values + if (intValue > 255 || intValue < Byte.MIN_VALUE) { + throw new JsonSyntaxException("Lossy conversion from " + intValue + " to byte; at path " + in.getPreviousPath()); + } + return (byte) intValue; } @Override public void write(JsonWriter out, Number value) throws IOException { @@ -201,11 +206,18 @@ public Number read(JsonReader in) throws IOException { in.nextNull(); return null; } + + int intValue; try { - return (short) in.nextInt(); + intValue = in.nextInt(); } catch (NumberFormatException e) { throw new JsonSyntaxException(e); } + // Allow up to 65535 to support unsigned values + if (intValue > 65535 || intValue < Short.MIN_VALUE) { + throw new JsonSyntaxException("Lossy conversion from " + intValue + " to short; at path " + in.getPreviousPath()); + } + return (short) intValue; } @Override public void write(JsonWriter out, Number value) throws IOException { @@ -352,7 +364,7 @@ public Character read(JsonReader in) throws IOException { } String str = in.nextString(); if (str.length() != 1) { - throw new JsonSyntaxException("Expecting character, got: " + str); + throw new JsonSyntaxException("Expecting character, got: " + str + "; at " + in.getPreviousPath()); } return str.charAt(0); } @@ -391,10 +403,11 @@ public void write(JsonWriter out, String value) throws IOException { in.nextNull(); return null; } + String s = in.nextString(); try { - return new BigDecimal(in.nextString()); + return new BigDecimal(s); } catch (NumberFormatException e) { - throw new JsonSyntaxException(e); + throw new JsonSyntaxException("Failed parsing '" + s + "' as BigDecimal; at path " + in.getPreviousPath(), e); } } @@ -409,10 +422,11 @@ public void write(JsonWriter out, String value) throws IOException { in.nextNull(); return null; } + String s = in.nextString(); try { - return new BigInteger(in.nextString()); + return new BigInteger(s); } catch (NumberFormatException e) { - throw new JsonSyntaxException(e); + throw new JsonSyntaxException("Failed parsing '" + s + "' as BigInteger; at path " + in.getPreviousPath(), e); } } @@ -525,7 +539,12 @@ public UUID read(JsonReader in) throws IOException { in.nextNull(); return null; } - return java.util.UUID.fromString(in.nextString()); + String s = in.nextString(); + try { + return java.util.UUID.fromString(s); + } catch (IllegalArgumentException e) { + throw new JsonSyntaxException("Failed parsing '" + s + "' as UUID; at path " + in.getPreviousPath(), e); + } } @Override public void write(JsonWriter out, UUID value) throws IOException { @@ -538,7 +557,12 @@ public void write(JsonWriter out, UUID value) throws IOException { public static final TypeAdapter CURRENCY = new TypeAdapter() { @Override public Currency read(JsonReader in) throws IOException { - return Currency.getInstance(in.nextString()); + String s = in.nextString(); + try { + return Currency.getInstance(s); + } catch (IllegalArgumentException e) { + throw new JsonSyntaxException("Failed parsing '" + s + "' as Currency; at path " + in.getPreviousPath(), e); + } } @Override public void write(JsonWriter out, Currency value) throws IOException { @@ -866,7 +890,7 @@ public static TypeAdapterFactory newTypeHierarchyFactory( T1 result = typeAdapter.read(in); if (result != null && !requestedType.isInstance(result)) { throw new JsonSyntaxException("Expected a " + requestedType.getName() - + " but was " + result.getClass().getName()); + + " but was " + result.getClass().getName() + "; at path " + in.getPreviousPath()); } return result; } diff --git a/gson/src/main/java/com/google/gson/internal/sql/SqlDateTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/sql/SqlDateTypeAdapter.java index b3da1fefd4..6ae4c3ef50 100644 --- a/gson/src/main/java/com/google/gson/internal/sql/SqlDateTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/sql/SqlDateTypeAdapter.java @@ -28,6 +28,7 @@ import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Date; /** * Adapter for java.sql.Date. Although this class appears stateless, it is not. @@ -50,21 +51,33 @@ private SqlDateTypeAdapter() { } @Override - public synchronized java.sql.Date read(JsonReader in) throws IOException { + public java.sql.Date read(JsonReader in) throws IOException { if (in.peek() == JsonToken.NULL) { in.nextNull(); return null; } + String s = in.nextString(); try { - final long utilDate = format.parse(in.nextString()).getTime(); - return new java.sql.Date(utilDate); + Date utilDate; + synchronized (this) { + utilDate = format.parse(s); + } + return new java.sql.Date(utilDate.getTime()); } catch (ParseException e) { - throw new JsonSyntaxException(e); + throw new JsonSyntaxException("Failed parsing '" + s + "' as SQL Date; at path " + in.getPreviousPath(), e); } } @Override - public synchronized void write(JsonWriter out, java.sql.Date value) throws IOException { - out.value(value == null ? null : format.format(value)); + public void write(JsonWriter out, java.sql.Date value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + String dateString; + synchronized (this) { + dateString = format.format(value); + } + out.value(dateString); } } diff --git a/gson/src/main/java/com/google/gson/internal/sql/SqlTimeTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/sql/SqlTimeTypeAdapter.java index ee65726b4e..c2a37073f0 100644 --- a/gson/src/main/java/com/google/gson/internal/sql/SqlTimeTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/sql/SqlTimeTypeAdapter.java @@ -50,20 +50,31 @@ final class SqlTimeTypeAdapter extends TypeAdapter

    + *
  • For JSON arrays the path points to the index of the previous element.
    + * If no element has been consumed yet it uses the index 0 (even if there are no elements).
  • + *
  • For JSON objects the path points to the last property, or to the current + * property if its value has not been consumed yet.
  • + *
+ * + *

This method can be useful to add additional context to exception messages + * after a value has been consumed. + */ + public String getPreviousPath() { + return getPath(true); + } + + /** + * Returns a JsonPath + * in dot-notation to the next (or current) location in the JSON document: + *

    + *
  • For JSON arrays the path points to the index of the next element (even + * if there are no further elements).
  • + *
  • For JSON objects the path points to the last property, or to the current + * property if its value has not been consumed yet.
  • + *
+ * + *

This method can be useful to add additional context to exception messages + * before a value is consumed, for example when the {@linkplain #peek() peeked} + * token is unexpected. + */ + public String getPath() { + return getPath(false); + } + /** * Unescapes the character identified by the character or characters that * immediately follow a backslash. The backslash '\' should have already @@ -1546,11 +1580,11 @@ private char readEscapeCharacter() throws IOException { case '\'': case '"': case '\\': - case '/': - return escaped; + case '/': + return escaped; default: - // throw error when none of the above cases are matched - throw syntaxError("Invalid escape sequence"); + // throw error when none of the above cases are matched + throw syntaxError("Invalid escape sequence"); } } diff --git a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java index f81537f5aa..e98e4a2960 100644 --- a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java @@ -332,6 +332,20 @@ public void testBitSetDeserialization() throws Exception { json = "[true,false,true,true,true,true,false,false,true,false,false]"; assertEquals(expected, gson.fromJson(json, BitSet.class)); + + try { + gson.fromJson("[1, []]", BitSet.class); + fail(); + } catch (JsonSyntaxException e) { + assertEquals("Invalid bitset value type: BEGIN_ARRAY; at path $[1]", e.getMessage()); + } + + try { + gson.fromJson("[1, 2]", BitSet.class); + fail(); + } catch (JsonSyntaxException e) { + assertEquals("Invalid bitset value 2, expected 0 or 1; at path $[1]", e.getMessage()); + } } public void testDefaultDateSerialization() { @@ -567,7 +581,7 @@ public void testJsonElementTypeMismatch() { gson.fromJson("\"abc\"", JsonObject.class); fail(); } catch (JsonSyntaxException expected) { - assertEquals("Expected a com.google.gson.JsonObject but was com.google.gson.JsonPrimitive", + assertEquals("Expected a com.google.gson.JsonObject but was com.google.gson.JsonPrimitive; at path $", expected.getMessage()); } } diff --git a/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java b/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java index be1f3b87f6..55e612fa46 100644 --- a/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java +++ b/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java @@ -16,6 +16,8 @@ package com.google.gson.functional; +import static org.junit.Assert.assertArrayEquals; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonPrimitive; @@ -63,16 +65,75 @@ public void testByteSerialization() { assertEquals("1", gson.toJson(1, Byte.class)); } + public void testByteDeserialization() { + Byte boxed = gson.fromJson("1", Byte.class); + assertEquals(1, (byte)boxed); + byte primitive = gson.fromJson("1", byte.class); + assertEquals(1, primitive); + + byte[] bytes = gson.fromJson("[-128, 0, 127, 255]", byte[].class); + assertArrayEquals(new byte[] {-128, 0, 127, -1}, bytes); + } + + public void testByteDeserializationLossy() { + try { + gson.fromJson("-129", byte.class); + fail(); + } catch (JsonSyntaxException e) { + assertEquals("Lossy conversion from -129 to byte; at path $", e.getMessage()); + } + + try { + gson.fromJson("256", byte.class); + fail(); + } catch (JsonSyntaxException e) { + assertEquals("Lossy conversion from 256 to byte; at path $", e.getMessage()); + } + + try { + gson.fromJson("2147483648", byte.class); + fail(); + } catch (JsonSyntaxException e) { + assertEquals("java.lang.NumberFormatException: Expected an int but was 2147483648 at line 1 column 11 path $", e.getMessage()); + } + } + public void testShortSerialization() { assertEquals("1", gson.toJson(1, short.class)); assertEquals("1", gson.toJson(1, Short.class)); } - public void testByteDeserialization() { - Byte target = gson.fromJson("1", Byte.class); - assertEquals(1, (byte)target); - byte primitive = gson.fromJson("1", byte.class); + public void testShortDeserialization() { + Short boxed = gson.fromJson("1", Short.class); + assertEquals(1, (short)boxed); + short primitive = gson.fromJson("1", short.class); assertEquals(1, primitive); + + short[] shorts = gson.fromJson("[-32768, 0, 32767, 65535]", short[].class); + assertArrayEquals(new short[] {-32768, 0, 32767, -1}, shorts); + } + + public void testShortDeserializationLossy() { + try { + gson.fromJson("-32769", short.class); + fail(); + } catch (JsonSyntaxException e) { + assertEquals("Lossy conversion from -32769 to short; at path $", e.getMessage()); + } + + try { + gson.fromJson("65536", short.class); + fail(); + } catch (JsonSyntaxException e) { + assertEquals("Lossy conversion from 65536 to short; at path $", e.getMessage()); + } + + try { + gson.fromJson("2147483648", short.class); + fail(); + } catch (JsonSyntaxException e) { + assertEquals("java.lang.NumberFormatException: Expected an int but was 2147483648 at line 1 column 11 path $", e.getMessage()); + } } public void testPrimitiveIntegerAutoboxedInASingleElementArraySerialization() { diff --git a/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java b/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java index 0fc5ed708a..04614de4e5 100644 --- a/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java +++ b/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java @@ -46,110 +46,154 @@ public static List parameters() { @Test public void path() throws IOException { JsonReader reader = factory.create("{\"a\":[2,true,false,null,\"b\",{\"c\":\"d\"},[3]]}"); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); reader.beginObject(); + assertEquals("$.", reader.getPreviousPath()); assertEquals("$.", reader.getPath()); reader.nextName(); + assertEquals("$.a", reader.getPreviousPath()); assertEquals("$.a", reader.getPath()); reader.beginArray(); + assertEquals("$.a[0]", reader.getPreviousPath()); assertEquals("$.a[0]", reader.getPath()); reader.nextInt(); + assertEquals("$.a[0]", reader.getPreviousPath()); assertEquals("$.a[1]", reader.getPath()); reader.nextBoolean(); + assertEquals("$.a[1]", reader.getPreviousPath()); assertEquals("$.a[2]", reader.getPath()); reader.nextBoolean(); + assertEquals("$.a[2]", reader.getPreviousPath()); assertEquals("$.a[3]", reader.getPath()); reader.nextNull(); + assertEquals("$.a[3]", reader.getPreviousPath()); assertEquals("$.a[4]", reader.getPath()); reader.nextString(); + assertEquals("$.a[4]", reader.getPreviousPath()); assertEquals("$.a[5]", reader.getPath()); reader.beginObject(); + assertEquals("$.a[5].", reader.getPreviousPath()); assertEquals("$.a[5].", reader.getPath()); reader.nextName(); + assertEquals("$.a[5].c", reader.getPreviousPath()); assertEquals("$.a[5].c", reader.getPath()); reader.nextString(); + assertEquals("$.a[5].c", reader.getPreviousPath()); assertEquals("$.a[5].c", reader.getPath()); reader.endObject(); + assertEquals("$.a[5]", reader.getPreviousPath()); assertEquals("$.a[6]", reader.getPath()); reader.beginArray(); + assertEquals("$.a[6][0]", reader.getPreviousPath()); assertEquals("$.a[6][0]", reader.getPath()); reader.nextInt(); + assertEquals("$.a[6][0]", reader.getPreviousPath()); assertEquals("$.a[6][1]", reader.getPath()); reader.endArray(); + assertEquals("$.a[6]", reader.getPreviousPath()); assertEquals("$.a[7]", reader.getPath()); reader.endArray(); + assertEquals("$.a", reader.getPreviousPath()); assertEquals("$.a", reader.getPath()); reader.endObject(); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); } @Test public void objectPath() throws IOException { JsonReader reader = factory.create("{\"a\":1,\"b\":2}"); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); reader.peek(); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); reader.beginObject(); + assertEquals("$.", reader.getPreviousPath()); assertEquals("$.", reader.getPath()); reader.peek(); + assertEquals("$.", reader.getPreviousPath()); assertEquals("$.", reader.getPath()); reader.nextName(); + assertEquals("$.a", reader.getPreviousPath()); assertEquals("$.a", reader.getPath()); reader.peek(); + assertEquals("$.a", reader.getPreviousPath()); assertEquals("$.a", reader.getPath()); reader.nextInt(); + assertEquals("$.a", reader.getPreviousPath()); assertEquals("$.a", reader.getPath()); reader.peek(); + assertEquals("$.a", reader.getPreviousPath()); assertEquals("$.a", reader.getPath()); reader.nextName(); + assertEquals("$.b", reader.getPreviousPath()); assertEquals("$.b", reader.getPath()); reader.peek(); + assertEquals("$.b", reader.getPreviousPath()); assertEquals("$.b", reader.getPath()); reader.nextInt(); + assertEquals("$.b", reader.getPreviousPath()); assertEquals("$.b", reader.getPath()); reader.peek(); + assertEquals("$.b", reader.getPreviousPath()); assertEquals("$.b", reader.getPath()); reader.endObject(); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); reader.peek(); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); reader.close(); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); } @Test public void arrayPath() throws IOException { JsonReader reader = factory.create("[1,2]"); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); reader.peek(); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); reader.beginArray(); + assertEquals("$[0]", reader.getPreviousPath()); assertEquals("$[0]", reader.getPath()); reader.peek(); + assertEquals("$[0]", reader.getPreviousPath()); assertEquals("$[0]", reader.getPath()); reader.nextInt(); + assertEquals("$[0]", reader.getPreviousPath()); assertEquals("$[1]", reader.getPath()); reader.peek(); + assertEquals("$[0]", reader.getPreviousPath()); assertEquals("$[1]", reader.getPath()); reader.nextInt(); + assertEquals("$[1]", reader.getPreviousPath()); assertEquals("$[2]", reader.getPath()); reader.peek(); + assertEquals("$[1]", reader.getPreviousPath()); assertEquals("$[2]", reader.getPath()); reader.endArray(); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); reader.peek(); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); reader.close(); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); } @@ -160,9 +204,11 @@ public static List parameters() { reader.setLenient(true); reader.beginArray(); reader.endArray(); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); reader.beginArray(); reader.endArray(); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); } @@ -171,6 +217,7 @@ public static List parameters() { reader.beginArray(); reader.skipValue(); reader.skipValue(); + assertEquals("$[1]", reader.getPreviousPath()); assertEquals("$[2]", reader.getPath()); } @@ -178,17 +225,21 @@ public static List parameters() { JsonReader reader = factory.create("{\"a\":1}"); reader.beginObject(); reader.skipValue(); + assertEquals("$.null", reader.getPreviousPath()); assertEquals("$.null", reader.getPath()); } @Test public void skipObjectValues() throws IOException { JsonReader reader = factory.create("{\"a\":1,\"b\":2}"); reader.beginObject(); + assertEquals("$.", reader.getPreviousPath()); assertEquals("$.", reader.getPath()); reader.nextName(); reader.skipValue(); + assertEquals("$.null", reader.getPreviousPath()); assertEquals("$.null", reader.getPath()); reader.nextName(); + assertEquals("$.b", reader.getPreviousPath()); assertEquals("$.b", reader.getPath()); } @@ -196,46 +247,63 @@ public static List parameters() { JsonReader reader = factory.create("[[1,2,3],4]"); reader.beginArray(); reader.skipValue(); + assertEquals("$[0]", reader.getPreviousPath()); assertEquals("$[1]", reader.getPath()); } @Test public void arrayOfObjects() throws IOException { JsonReader reader = factory.create("[{},{},{}]"); reader.beginArray(); + assertEquals("$[0]", reader.getPreviousPath()); assertEquals("$[0]", reader.getPath()); reader.beginObject(); + assertEquals("$[0].", reader.getPreviousPath()); assertEquals("$[0].", reader.getPath()); reader.endObject(); + assertEquals("$[0]", reader.getPreviousPath()); assertEquals("$[1]", reader.getPath()); reader.beginObject(); + assertEquals("$[1].", reader.getPreviousPath()); assertEquals("$[1].", reader.getPath()); reader.endObject(); + assertEquals("$[1]", reader.getPreviousPath()); assertEquals("$[2]", reader.getPath()); reader.beginObject(); + assertEquals("$[2].", reader.getPreviousPath()); assertEquals("$[2].", reader.getPath()); reader.endObject(); + assertEquals("$[2]", reader.getPreviousPath()); assertEquals("$[3]", reader.getPath()); reader.endArray(); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); } @Test public void arrayOfArrays() throws IOException { JsonReader reader = factory.create("[[],[],[]]"); reader.beginArray(); + assertEquals("$[0]", reader.getPreviousPath()); assertEquals("$[0]", reader.getPath()); reader.beginArray(); + assertEquals("$[0][0]", reader.getPreviousPath()); assertEquals("$[0][0]", reader.getPath()); reader.endArray(); + assertEquals("$[0]", reader.getPreviousPath()); assertEquals("$[1]", reader.getPath()); reader.beginArray(); + assertEquals("$[1][0]", reader.getPreviousPath()); assertEquals("$[1][0]", reader.getPath()); reader.endArray(); + assertEquals("$[1]", reader.getPreviousPath()); assertEquals("$[2]", reader.getPath()); reader.beginArray(); + assertEquals("$[2][0]", reader.getPreviousPath()); assertEquals("$[2][0]", reader.getPath()); reader.endArray(); + assertEquals("$[2]", reader.getPreviousPath()); assertEquals("$[3]", reader.getPath()); reader.endArray(); + assertEquals("$", reader.getPreviousPath()); assertEquals("$", reader.getPath()); } From e0de45ff69ba3daacc3b7623cc74fc69a4eaf6d0 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 1 Nov 2021 23:09:14 +0100 Subject: [PATCH 65/90] Delete unused LinkedHashTreeMap (#1992) Class seems to be unused since commit f29d5bc37b52c4b8d2ad15a10bb0c7f684c1d45d. Gson currently only uses LinkedTreeMap. --- .../gson/internal/LinkedHashTreeMap.java | 872 ------------------ .../gson/internal/LinkedHashTreeMapTest.java | 312 ------- 2 files changed, 1184 deletions(-) delete mode 100644 gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java delete mode 100644 gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java diff --git a/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java b/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java deleted file mode 100644 index 0cade0d1f8..0000000000 --- a/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java +++ /dev/null @@ -1,872 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * Copyright (C) 2012 Google Inc. - * - * 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 com.google.gson.internal; - -import java.io.IOException; -import java.io.InvalidObjectException; -import java.io.ObjectInputStream; -import java.io.ObjectStreamException; -import java.io.Serializable; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Arrays; -import java.util.Comparator; -import java.util.ConcurrentModificationException; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.NoSuchElementException; -import java.util.Set; - -/** - * A map of comparable keys to values. Unlike {@code TreeMap}, this class uses - * insertion order for iteration order. Comparison order is only used as an - * optimization for efficient insertion and removal. - * - *

This implementation was derived from Android 4.1's TreeMap and - * LinkedHashMap classes. - */ -public final class LinkedHashTreeMap extends AbstractMap implements Serializable { - @SuppressWarnings({ "unchecked", "rawtypes" }) // to avoid Comparable>> - private static final Comparator NATURAL_ORDER = new Comparator() { - public int compare(Comparable a, Comparable b) { - return a.compareTo(b); - } - }; - - Comparator comparator; - Node[] table; - final Node header; - int size = 0; - int modCount = 0; - int threshold; - - /** - * Create a natural order, empty tree map whose keys must be mutually - * comparable and non-null. - */ - @SuppressWarnings("unchecked") // unsafe! this assumes K is comparable - public LinkedHashTreeMap() { - this((Comparator) NATURAL_ORDER); - } - - /** - * Create a tree map ordered by {@code comparator}. This map's keys may only - * be null if {@code comparator} permits. - * - * @param comparator the comparator to order elements with, or {@code null} to - * use the natural ordering. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) // unsafe! if comparator is null, this assumes K is comparable - public LinkedHashTreeMap(Comparator comparator) { - this.comparator = comparator != null - ? comparator - : (Comparator) NATURAL_ORDER; - this.header = new Node(); - this.table = new Node[16]; // TODO: sizing/resizing policies - this.threshold = (table.length / 2) + (table.length / 4); // 3/4 capacity - } - - @Override public int size() { - return size; - } - - @Override public V get(Object key) { - Node node = findByObject(key); - return node != null ? node.value : null; - } - - @Override public boolean containsKey(Object key) { - return findByObject(key) != null; - } - - @Override public V put(K key, V value) { - if (key == null) { - throw new NullPointerException("key == null"); - } - Node created = find(key, true); - V result = created.value; - created.value = value; - return result; - } - - @Override public void clear() { - Arrays.fill(table, null); - size = 0; - modCount++; - - // Clear all links to help GC - Node header = this.header; - for (Node e = header.next; e != header; ) { - Node next = e.next; - e.next = e.prev = null; - e = next; - } - - header.next = header.prev = header; - } - - @Override public V remove(Object key) { - Node node = removeInternalByKey(key); - return node != null ? node.value : null; - } - - /** - * Returns the node at or adjacent to the given key, creating it if requested. - * - * @throws ClassCastException if {@code key} and the tree's keys aren't - * mutually comparable. - */ - Node find(K key, boolean create) { - Comparator comparator = this.comparator; - Node[] table = this.table; - int hash = secondaryHash(key.hashCode()); - int index = hash & (table.length - 1); - Node nearest = table[index]; - int comparison = 0; - - if (nearest != null) { - // Micro-optimization: avoid polymorphic calls to Comparator.compare(). - @SuppressWarnings("unchecked") // Throws a ClassCastException below if there's trouble. - Comparable comparableKey = (comparator == NATURAL_ORDER) - ? (Comparable) key - : null; - - while (true) { - comparison = (comparableKey != null) - ? comparableKey.compareTo(nearest.key) - : comparator.compare(key, nearest.key); - - // We found the requested key. - if (comparison == 0) { - return nearest; - } - - // If it exists, the key is in a subtree. Go deeper. - Node child = (comparison < 0) ? nearest.left : nearest.right; - if (child == null) { - break; - } - - nearest = child; - } - } - - // The key doesn't exist in this tree. - if (!create) { - return null; - } - - // Create the node and add it to the tree or the table. - Node header = this.header; - Node created; - if (nearest == null) { - // Check that the value is comparable if we didn't do any comparisons. - if (comparator == NATURAL_ORDER && !(key instanceof Comparable)) { - throw new ClassCastException(key.getClass().getName() + " is not Comparable"); - } - created = new Node(nearest, key, hash, header, header.prev); - table[index] = created; - } else { - created = new Node(nearest, key, hash, header, header.prev); - if (comparison < 0) { // nearest.key is higher - nearest.left = created; - } else { // comparison > 0, nearest.key is lower - nearest.right = created; - } - rebalance(nearest, true); - } - - if (size++ > threshold) { - doubleCapacity(); - } - modCount++; - - return created; - } - - @SuppressWarnings("unchecked") - Node findByObject(Object key) { - try { - return key != null ? find((K) key, false) : null; - } catch (ClassCastException e) { - return null; - } - } - - /** - * Returns this map's entry that has the same key and value as {@code - * entry}, or null if this map has no such entry. - * - *

This method uses the comparator for key equality rather than {@code - * equals}. If this map's comparator isn't consistent with equals (such as - * {@code String.CASE_INSENSITIVE_ORDER}), then {@code remove()} and {@code - * contains()} will violate the collections API. - */ - Node findByEntry(Entry entry) { - Node mine = findByObject(entry.getKey()); - boolean valuesEqual = mine != null && equal(mine.value, entry.getValue()); - return valuesEqual ? mine : null; - } - - private boolean equal(Object a, Object b) { - return a == b || (a != null && a.equals(b)); - } - - /** - * Applies a supplemental hash function to a given hashCode, which defends - * against poor quality hash functions. This is critical because HashMap - * uses power-of-two length hash tables, that otherwise encounter collisions - * for hashCodes that do not differ in lower or upper bits. - */ - private static int secondaryHash(int h) { - // Doug Lea's supplemental hash function - h ^= (h >>> 20) ^ (h >>> 12); - return h ^ (h >>> 7) ^ (h >>> 4); - } - - /** - * Removes {@code node} from this tree, rearranging the tree's structure as - * necessary. - * - * @param unlink true to also unlink this node from the iteration linked list. - */ - void removeInternal(Node node, boolean unlink) { - if (unlink) { - node.prev.next = node.next; - node.next.prev = node.prev; - node.next = node.prev = null; // Help the GC (for performance) - } - - Node left = node.left; - Node right = node.right; - Node originalParent = node.parent; - if (left != null && right != null) { - - /* - * To remove a node with both left and right subtrees, move an - * adjacent node from one of those subtrees into this node's place. - * - * Removing the adjacent node may change this node's subtrees. This - * node may no longer have two subtrees once the adjacent node is - * gone! - */ - - Node adjacent = (left.height > right.height) ? left.last() : right.first(); - removeInternal(adjacent, false); // takes care of rebalance and size-- - - int leftHeight = 0; - left = node.left; - if (left != null) { - leftHeight = left.height; - adjacent.left = left; - left.parent = adjacent; - node.left = null; - } - int rightHeight = 0; - right = node.right; - if (right != null) { - rightHeight = right.height; - adjacent.right = right; - right.parent = adjacent; - node.right = null; - } - adjacent.height = Math.max(leftHeight, rightHeight) + 1; - replaceInParent(node, adjacent); - return; - } else if (left != null) { - replaceInParent(node, left); - node.left = null; - } else if (right != null) { - replaceInParent(node, right); - node.right = null; - } else { - replaceInParent(node, null); - } - - rebalance(originalParent, false); - size--; - modCount++; - } - - Node removeInternalByKey(Object key) { - Node node = findByObject(key); - if (node != null) { - removeInternal(node, true); - } - return node; - } - - private void replaceInParent(Node node, Node replacement) { - Node parent = node.parent; - node.parent = null; - if (replacement != null) { - replacement.parent = parent; - } - - if (parent != null) { - if (parent.left == node) { - parent.left = replacement; - } else { - assert (parent.right == node); - parent.right = replacement; - } - } else { - int index = node.hash & (table.length - 1); - table[index] = replacement; - } - } - - /** - * Rebalances the tree by making any AVL rotations necessary between the - * newly-unbalanced node and the tree's root. - * - * @param insert true if the node was unbalanced by an insert; false if it - * was by a removal. - */ - private void rebalance(Node unbalanced, boolean insert) { - for (Node node = unbalanced; node != null; node = node.parent) { - Node left = node.left; - Node right = node.right; - int leftHeight = left != null ? left.height : 0; - int rightHeight = right != null ? right.height : 0; - - int delta = leftHeight - rightHeight; - if (delta == -2) { - Node rightLeft = right.left; - Node rightRight = right.right; - int rightRightHeight = rightRight != null ? rightRight.height : 0; - int rightLeftHeight = rightLeft != null ? rightLeft.height : 0; - - int rightDelta = rightLeftHeight - rightRightHeight; - if (rightDelta == -1 || (rightDelta == 0 && !insert)) { - rotateLeft(node); // AVL right right - } else { - assert (rightDelta == 1); - rotateRight(right); // AVL right left - rotateLeft(node); - } - if (insert) { - break; // no further rotations will be necessary - } - - } else if (delta == 2) { - Node leftLeft = left.left; - Node leftRight = left.right; - int leftRightHeight = leftRight != null ? leftRight.height : 0; - int leftLeftHeight = leftLeft != null ? leftLeft.height : 0; - - int leftDelta = leftLeftHeight - leftRightHeight; - if (leftDelta == 1 || (leftDelta == 0 && !insert)) { - rotateRight(node); // AVL left left - } else { - assert (leftDelta == -1); - rotateLeft(left); // AVL left right - rotateRight(node); - } - if (insert) { - break; // no further rotations will be necessary - } - - } else if (delta == 0) { - node.height = leftHeight + 1; // leftHeight == rightHeight - if (insert) { - break; // the insert caused balance, so rebalancing is done! - } - - } else { - assert (delta == -1 || delta == 1); - node.height = Math.max(leftHeight, rightHeight) + 1; - if (!insert) { - break; // the height hasn't changed, so rebalancing is done! - } - } - } - } - - /** - * Rotates the subtree so that its root's right child is the new root. - */ - private void rotateLeft(Node root) { - Node left = root.left; - Node pivot = root.right; - Node pivotLeft = pivot.left; - Node pivotRight = pivot.right; - - // move the pivot's left child to the root's right - root.right = pivotLeft; - if (pivotLeft != null) { - pivotLeft.parent = root; - } - - replaceInParent(root, pivot); - - // move the root to the pivot's left - pivot.left = root; - root.parent = pivot; - - // fix heights - root.height = Math.max(left != null ? left.height : 0, - pivotLeft != null ? pivotLeft.height : 0) + 1; - pivot.height = Math.max(root.height, - pivotRight != null ? pivotRight.height : 0) + 1; - } - - /** - * Rotates the subtree so that its root's left child is the new root. - */ - private void rotateRight(Node root) { - Node pivot = root.left; - Node right = root.right; - Node pivotLeft = pivot.left; - Node pivotRight = pivot.right; - - // move the pivot's right child to the root's left - root.left = pivotRight; - if (pivotRight != null) { - pivotRight.parent = root; - } - - replaceInParent(root, pivot); - - // move the root to the pivot's right - pivot.right = root; - root.parent = pivot; - - // fixup heights - root.height = Math.max(right != null ? right.height : 0, - pivotRight != null ? pivotRight.height : 0) + 1; - pivot.height = Math.max(root.height, - pivotLeft != null ? pivotLeft.height : 0) + 1; - } - - private EntrySet entrySet; - private KeySet keySet; - - @Override public Set> entrySet() { - EntrySet result = entrySet; - return result != null ? result : (entrySet = new EntrySet()); - } - - @Override public Set keySet() { - KeySet result = keySet; - return result != null ? result : (keySet = new KeySet()); - } - - static final class Node implements Entry { - Node parent; - Node left; - Node right; - Node next; - Node prev; - final K key; - final int hash; - V value; - int height; - - /** Create the header entry */ - Node() { - key = null; - hash = -1; - next = prev = this; - } - - /** Create a regular entry */ - Node(Node parent, K key, int hash, Node next, Node prev) { - this.parent = parent; - this.key = key; - this.hash = hash; - this.height = 1; - this.next = next; - this.prev = prev; - prev.next = this; - next.prev = this; - } - - public K getKey() { - return key; - } - - public V getValue() { - return value; - } - - public V setValue(V value) { - V oldValue = this.value; - this.value = value; - return oldValue; - } - - @SuppressWarnings("rawtypes") - @Override public boolean equals(Object o) { - if (o instanceof Entry) { - Entry other = (Entry) o; - return (key == null ? other.getKey() == null : key.equals(other.getKey())) - && (value == null ? other.getValue() == null : value.equals(other.getValue())); - } - return false; - } - - @Override public int hashCode() { - return (key == null ? 0 : key.hashCode()) - ^ (value == null ? 0 : value.hashCode()); - } - - @Override public String toString() { - return key + "=" + value; - } - - /** - * Returns the first node in this subtree. - */ - public Node first() { - Node node = this; - Node child = node.left; - while (child != null) { - node = child; - child = node.left; - } - return node; - } - - /** - * Returns the last node in this subtree. - */ - public Node last() { - Node node = this; - Node child = node.right; - while (child != null) { - node = child; - child = node.right; - } - return node; - } - } - - private void doubleCapacity() { - table = doubleCapacity(table); - threshold = (table.length / 2) + (table.length / 4); // 3/4 capacity - } - - /** - * Returns a new array containing the same nodes as {@code oldTable}, but with - * twice as many trees, each of (approximately) half the previous size. - */ - static Node[] doubleCapacity(Node[] oldTable) { - // TODO: don't do anything if we're already at MAX_CAPACITY - int oldCapacity = oldTable.length; - @SuppressWarnings("unchecked") // Arrays and generics don't get along. - Node[] newTable = new Node[oldCapacity * 2]; - AvlIterator iterator = new AvlIterator(); - AvlBuilder leftBuilder = new AvlBuilder(); - AvlBuilder rightBuilder = new AvlBuilder(); - - // Split each tree into two trees. - for (int i = 0; i < oldCapacity; i++) { - Node root = oldTable[i]; - if (root == null) { - continue; - } - - // Compute the sizes of the left and right trees. - iterator.reset(root); - int leftSize = 0; - int rightSize = 0; - for (Node node; (node = iterator.next()) != null; ) { - if ((node.hash & oldCapacity) == 0) { - leftSize++; - } else { - rightSize++; - } - } - - // Split the tree into two. - leftBuilder.reset(leftSize); - rightBuilder.reset(rightSize); - iterator.reset(root); - for (Node node; (node = iterator.next()) != null; ) { - if ((node.hash & oldCapacity) == 0) { - leftBuilder.add(node); - } else { - rightBuilder.add(node); - } - } - - // Populate the enlarged array with these new roots. - newTable[i] = leftSize > 0 ? leftBuilder.root() : null; - newTable[i + oldCapacity] = rightSize > 0 ? rightBuilder.root() : null; - } - return newTable; - } - - /** - * Walks an AVL tree in iteration order. Once a node has been returned, its - * left, right and parent links are no longer used. For this - * reason it is safe to transform these links as you walk a tree. - * - *

Warning: this iterator is destructive. It clears the - * parent node of all nodes in the tree. It is an error to make a partial - * iteration of a tree. - */ - static class AvlIterator { - /** This stack is a singly linked list, linked by the 'parent' field. */ - private Node stackTop; - - void reset(Node root) { - Node stackTop = null; - for (Node n = root; n != null; n = n.left) { - n.parent = stackTop; - stackTop = n; // Stack push. - } - this.stackTop = stackTop; - } - - public Node next() { - Node stackTop = this.stackTop; - if (stackTop == null) { - return null; - } - Node result = stackTop; - stackTop = result.parent; - result.parent = null; - for (Node n = result.right; n != null; n = n.left) { - n.parent = stackTop; - stackTop = n; // Stack push. - } - this.stackTop = stackTop; - return result; - } - } - - /** - * Builds AVL trees of a predetermined size by accepting nodes of increasing - * value. To use: - *

    - *
  1. Call {@link #reset} to initialize the target size size. - *
  2. Call {@link #add} size times with increasing values. - *
  3. Call {@link #root} to get the root of the balanced tree. - *
- * - *

The returned tree will satisfy the AVL constraint: for every node - * N, the height of N.left and N.right is different by at - * most 1. It accomplishes this by omitting deepest-level leaf nodes when - * building trees whose size isn't a power of 2 minus 1. - * - *

Unlike rebuilding a tree from scratch, this approach requires no value - * comparisons. Using this class to create a tree of size S is - * {@code O(S)}. - */ - final static class AvlBuilder { - /** This stack is a singly linked list, linked by the 'parent' field. */ - private Node stack; - private int leavesToSkip; - private int leavesSkipped; - private int size; - - void reset(int targetSize) { - // compute the target tree size. This is a power of 2 minus one, like 15 or 31. - int treeCapacity = Integer.highestOneBit(targetSize) * 2 - 1; - leavesToSkip = treeCapacity - targetSize; - size = 0; - leavesSkipped = 0; - stack = null; - } - - void add(Node node) { - node.left = node.parent = node.right = null; - node.height = 1; - - // Skip a leaf if necessary. - if (leavesToSkip > 0 && (size & 1) == 0) { - size++; - leavesToSkip--; - leavesSkipped++; - } - - node.parent = stack; - stack = node; // Stack push. - size++; - - // Skip a leaf if necessary. - if (leavesToSkip > 0 && (size & 1) == 0) { - size++; - leavesToSkip--; - leavesSkipped++; - } - - /* - * Combine 3 nodes into subtrees whenever the size is one less than a - * multiple of 4. For example we combine the nodes A, B, C into a - * 3-element tree with B as the root. - * - * Combine two subtrees and a spare single value whenever the size is one - * less than a multiple of 8. For example at 8 we may combine subtrees - * (A B C) and (E F G) with D as the root to form ((A B C) D (E F G)). - * - * Just as we combine single nodes when size nears a multiple of 4, and - * 3-element trees when size nears a multiple of 8, we combine subtrees of - * size (N-1) whenever the total size is 2N-1 whenever N is a power of 2. - */ - for (int scale = 4; (size & scale - 1) == scale - 1; scale *= 2) { - if (leavesSkipped == 0) { - // Pop right, center and left, then make center the top of the stack. - Node right = stack; - Node center = right.parent; - Node left = center.parent; - center.parent = left.parent; - stack = center; - // Construct a tree. - center.left = left; - center.right = right; - center.height = right.height + 1; - left.parent = center; - right.parent = center; - } else if (leavesSkipped == 1) { - // Pop right and center, then make center the top of the stack. - Node right = stack; - Node center = right.parent; - stack = center; - // Construct a tree with no left child. - center.right = right; - center.height = right.height + 1; - right.parent = center; - leavesSkipped = 0; - } else if (leavesSkipped == 2) { - leavesSkipped = 0; - } - } - } - - Node root() { - Node stackTop = this.stack; - if (stackTop.parent != null) { - throw new IllegalStateException(); - } - return stackTop; - } - } - - private abstract class LinkedTreeMapIterator implements Iterator { - Node next = header.next; - Node lastReturned = null; - int expectedModCount = modCount; - - LinkedTreeMapIterator() { - } - - public final boolean hasNext() { - return next != header; - } - - final Node nextNode() { - Node e = next; - if (e == header) { - throw new NoSuchElementException(); - } - if (modCount != expectedModCount) { - throw new ConcurrentModificationException(); - } - next = e.next; - return lastReturned = e; - } - - public final void remove() { - if (lastReturned == null) { - throw new IllegalStateException(); - } - removeInternal(lastReturned, true); - lastReturned = null; - expectedModCount = modCount; - } - } - - final class EntrySet extends AbstractSet> { - @Override public int size() { - return size; - } - - @Override public Iterator> iterator() { - return new LinkedTreeMapIterator>() { - public Entry next() { - return nextNode(); - } - }; - } - - @Override public boolean contains(Object o) { - return o instanceof Entry && findByEntry((Entry) o) != null; - } - - @Override public boolean remove(Object o) { - if (!(o instanceof Entry)) { - return false; - } - - Node node = findByEntry((Entry) o); - if (node == null) { - return false; - } - removeInternal(node, true); - return true; - } - - @Override public void clear() { - LinkedHashTreeMap.this.clear(); - } - } - - final class KeySet extends AbstractSet { - @Override public int size() { - return size; - } - - @Override public Iterator iterator() { - return new LinkedTreeMapIterator() { - public K next() { - return nextNode().key; - } - }; - } - - @Override public boolean contains(Object o) { - return containsKey(o); - } - - @Override public boolean remove(Object key) { - return removeInternalByKey(key) != null; - } - - @Override public void clear() { - LinkedHashTreeMap.this.clear(); - } - } - - /** - * If somebody is unlucky enough to have to serialize one of these, serialize - * it as a LinkedHashMap so that they won't need Gson on the other side to - * deserialize it. Using serialization defeats our DoS defence, so most apps - * shouldn't use it. - */ - private Object writeReplace() throws ObjectStreamException { - return new LinkedHashMap(this); - } - - private void readObject(ObjectInputStream in) throws IOException { - // Don't permit directly deserializing this class; writeReplace() should have written a replacement - throw new InvalidObjectException("Deserialization is unsupported"); - } -} diff --git a/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java b/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java deleted file mode 100644 index 65864f0c97..0000000000 --- a/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. - * - * 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 com.google.gson.internal; - -import com.google.gson.common.MoreAsserts; -import com.google.gson.internal.LinkedHashTreeMap.AvlBuilder; -import com.google.gson.internal.LinkedHashTreeMap.AvlIterator; -import com.google.gson.internal.LinkedHashTreeMap.Node; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.Map; -import java.util.Random; -import junit.framework.TestCase; - -public final class LinkedHashTreeMapTest extends TestCase { - public void testIterationOrder() { - LinkedHashTreeMap map = new LinkedHashTreeMap(); - map.put("a", "android"); - map.put("c", "cola"); - map.put("b", "bbq"); - assertIterationOrder(map.keySet(), "a", "c", "b"); - assertIterationOrder(map.values(), "android", "cola", "bbq"); - } - - public void testRemoveRootDoesNotDoubleUnlink() { - LinkedHashTreeMap map = new LinkedHashTreeMap(); - map.put("a", "android"); - map.put("c", "cola"); - map.put("b", "bbq"); - Iterator> it = map.entrySet().iterator(); - it.next(); - it.next(); - it.next(); - it.remove(); - assertIterationOrder(map.keySet(), "a", "c"); - } - - public void testPutNullKeyFails() { - LinkedHashTreeMap map = new LinkedHashTreeMap(); - try { - map.put(null, "android"); - fail(); - } catch (NullPointerException expected) { - } - } - - public void testPutNonComparableKeyFails() { - LinkedHashTreeMap map = new LinkedHashTreeMap(); - try { - map.put(new Object(), "android"); - fail(); - } catch (ClassCastException expected) {} - } - - public void testContainsNonComparableKeyReturnsFalse() { - LinkedHashTreeMap map = new LinkedHashTreeMap(); - map.put("a", "android"); - assertFalse(map.containsKey(new Object())); - } - - public void testContainsNullKeyIsAlwaysFalse() { - LinkedHashTreeMap map = new LinkedHashTreeMap(); - map.put("a", "android"); - assertFalse(map.containsKey(null)); - } - - public void testPutOverrides() throws Exception { - LinkedHashTreeMap map = new LinkedHashTreeMap(); - assertNull(map.put("d", "donut")); - assertNull(map.put("e", "eclair")); - assertNull(map.put("f", "froyo")); - assertEquals(3, map.size()); - - assertEquals("donut", map.get("d")); - assertEquals("donut", map.put("d", "done")); - assertEquals(3, map.size()); - } - - public void testEmptyStringValues() { - LinkedHashTreeMap map = new LinkedHashTreeMap(); - map.put("a", ""); - assertTrue(map.containsKey("a")); - assertEquals("", map.get("a")); - } - - // NOTE that this does not happen every time, but given the below predictable random, - // this test will consistently fail (assuming the initial size is 16 and rehashing - // size remains at 3/4) - public void testForceDoublingAndRehash() throws Exception { - Random random = new Random(1367593214724L); - LinkedHashTreeMap map = new LinkedHashTreeMap(); - String[] keys = new String[1000]; - for (int i = 0; i < keys.length; i++) { - keys[i] = Integer.toString(Math.abs(random.nextInt()), 36) + "-" + i; - map.put(keys[i], "" + i); - } - - for (int i = 0; i < keys.length; i++) { - String key = keys[i]; - assertTrue(map.containsKey(key)); - assertEquals("" + i, map.get(key)); - } - } - - public void testClear() { - LinkedHashTreeMap map = new LinkedHashTreeMap(); - map.put("a", "android"); - map.put("c", "cola"); - map.put("b", "bbq"); - map.clear(); - assertIterationOrder(map.keySet()); - assertEquals(0, map.size()); - } - - public void testEqualsAndHashCode() throws Exception { - LinkedHashTreeMap map1 = new LinkedHashTreeMap(); - map1.put("A", 1); - map1.put("B", 2); - map1.put("C", 3); - map1.put("D", 4); - - LinkedHashTreeMap map2 = new LinkedHashTreeMap(); - map2.put("C", 3); - map2.put("B", 2); - map2.put("D", 4); - map2.put("A", 1); - - MoreAsserts.assertEqualsAndHashCode(map1, map2); - } - - public void testAvlWalker() { - assertAvlWalker(node(node("a"), "b", node("c")), - "a", "b", "c"); - assertAvlWalker(node(node(node("a"), "b", node("c")), "d", node(node("e"), "f", node("g"))), - "a", "b", "c", "d", "e", "f", "g"); - assertAvlWalker(node(node(null, "a", node("b")), "c", node(node("d"), "e", null)), - "a", "b", "c", "d", "e"); - assertAvlWalker(node(null, "a", node(null, "b", node(null, "c", node("d")))), - "a", "b", "c", "d"); - assertAvlWalker(node(node(node(node("a"), "b", null), "c", null), "d", null), - "a", "b", "c", "d"); - } - - private void assertAvlWalker(Node root, String... values) { - AvlIterator iterator = new AvlIterator(); - iterator.reset(root); - for (String value : values) { - assertEquals(value, iterator.next().getKey()); - } - assertNull(iterator.next()); - } - - public void testAvlBuilder() { - assertAvlBuilder(1, "a"); - assertAvlBuilder(2, "(. a b)"); - assertAvlBuilder(3, "(a b c)"); - assertAvlBuilder(4, "(a b (. c d))"); - assertAvlBuilder(5, "(a b (c d e))"); - assertAvlBuilder(6, "((. a b) c (d e f))"); - assertAvlBuilder(7, "((a b c) d (e f g))"); - assertAvlBuilder(8, "((a b c) d (e f (. g h)))"); - assertAvlBuilder(9, "((a b c) d (e f (g h i)))"); - assertAvlBuilder(10, "((a b c) d ((. e f) g (h i j)))"); - assertAvlBuilder(11, "((a b c) d ((e f g) h (i j k)))"); - assertAvlBuilder(12, "((a b (. c d)) e ((f g h) i (j k l)))"); - assertAvlBuilder(13, "((a b (c d e)) f ((g h i) j (k l m)))"); - assertAvlBuilder(14, "(((. a b) c (d e f)) g ((h i j) k (l m n)))"); - assertAvlBuilder(15, "(((a b c) d (e f g)) h ((i j k) l (m n o)))"); - assertAvlBuilder(16, "(((a b c) d (e f g)) h ((i j k) l (m n (. o p))))"); - assertAvlBuilder(30, "((((. a b) c (d e f)) g ((h i j) k (l m n))) o " - + "(((p q r) s (t u v)) w ((x y z) A (B C D))))"); - assertAvlBuilder(31, "((((a b c) d (e f g)) h ((i j k) l (m n o))) p " - + "(((q r s) t (u v w)) x ((y z A) B (C D E))))"); - } - - private void assertAvlBuilder(int size, String expected) { - char[] values = "abcdefghijklmnopqrstuvwxyzABCDE".toCharArray(); - AvlBuilder avlBuilder = new AvlBuilder(); - avlBuilder.reset(size); - for (int i = 0; i < size; i++) { - avlBuilder.add(node(Character.toString(values[i]))); - } - assertTree(expected, avlBuilder.root()); - } - - public void testDoubleCapacity() { - @SuppressWarnings("unchecked") // Arrays and generics don't get along. - Node[] oldTable = new Node[1]; - oldTable[0] = node(node(node("a"), "b", node("c")), "d", node(node("e"), "f", node("g"))); - - Node[] newTable = LinkedHashTreeMap.doubleCapacity(oldTable); - assertTree("(b d f)", newTable[0]); // Even hash codes! - assertTree("(a c (. e g))", newTable[1]); // Odd hash codes! - } - - public void testDoubleCapacityAllNodesOnLeft() { - @SuppressWarnings("unchecked") // Arrays and generics don't get along. - Node[] oldTable = new Node[1]; - oldTable[0] = node(node("b"), "d", node("f")); - - Node[] newTable = LinkedHashTreeMap.doubleCapacity(oldTable); - assertTree("(b d f)", newTable[0]); // Even hash codes! - assertNull(newTable[1]); // Odd hash codes! - - for (Node node : newTable) { - if (node != null) { - assertConsistent(node); - } - } - } - - public void testJavaSerialization() throws IOException, ClassNotFoundException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ObjectOutputStream objOut = new ObjectOutputStream(out); - Map map = new LinkedHashTreeMap(); - map.put("a", 1); - objOut.writeObject(map); - objOut.close(); - - ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray())); - @SuppressWarnings("unchecked") - Map deserialized = (Map) objIn.readObject(); - assertEquals(Collections.singletonMap("a", 1), deserialized); - } - - private static final Node head = new Node(); - - private Node node(String value) { - return new Node(null, value, value.hashCode(), head, head); - } - - private Node node(Node left, String value, - Node right) { - Node result = node(value); - if (left != null) { - result.left = left; - left.parent = result; - } - if (right != null) { - result.right = right; - right.parent = result; - } - return result; - } - - private void assertTree(String expected, Node root) { - assertEquals(expected, toString(root)); - assertConsistent(root); - } - - private void assertConsistent(Node node) { - int leftHeight = 0; - if (node.left != null) { - assertConsistent(node.left); - assertSame(node, node.left.parent); - leftHeight = node.left.height; - } - int rightHeight = 0; - if (node.right != null) { - assertConsistent(node.right); - assertSame(node, node.right.parent); - rightHeight = node.right.height; - } - if (node.parent != null) { - assertTrue(node.parent.left == node || node.parent.right == node); - } - if (Math.max(leftHeight, rightHeight) + 1 != node.height) { - fail(); - } - } - - private String toString(Node root) { - if (root == null) { - return "."; - } else if (root.left == null && root.right == null) { - return String.valueOf(root.key); - } else { - return String.format("(%s %s %s)", toString(root.left), root.key, toString(root.right)); - } - } - - @SafeVarargs - private void assertIterationOrder(Iterable actual, T... expected) { - ArrayList actualList = new ArrayList(); - for (T t : actual) { - actualList.add(t); - } - assertEquals(Arrays.asList(expected), actualList); - } -} From deaa3a6cd9f4676e0c826eadadd2f3d6dc857096 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 1 Nov 2021 23:11:10 +0100 Subject: [PATCH 66/90] Fix `Gson.newJsonWriter` ignoring lenient and HTML-safe setting (#1989) * Improve Gson newJsonWriter and newJsonReader documentation * Consider lenient and HTML-safe setting for Gson.newJsonWriter * Remove empty line between imports --- gson/src/main/java/com/google/gson/Gson.java | 16 +++++ .../test/java/com/google/gson/GsonTest.java | 70 +++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index ef7b875195..2015b4cafe 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -778,6 +778,15 @@ public void toJson(JsonElement jsonElement, Appendable writer) throws JsonIOExce /** * Returns a new JSON writer configured for the settings on this Gson instance. + * + *

The following settings are considered: + *

    + *
  • {@link GsonBuilder#disableHtmlEscaping()}
  • + *
  • {@link GsonBuilder#generateNonExecutableJson()}
  • + *
  • {@link GsonBuilder#serializeNulls()}
  • + *
  • {@link GsonBuilder#setLenient()}
  • + *
  • {@link GsonBuilder#setPrettyPrinting()}
  • + *
*/ public JsonWriter newJsonWriter(Writer writer) throws IOException { if (generateNonExecutableJson) { @@ -787,12 +796,19 @@ public JsonWriter newJsonWriter(Writer writer) throws IOException { if (prettyPrinting) { jsonWriter.setIndent(" "); } + jsonWriter.setHtmlSafe(htmlSafe); + jsonWriter.setLenient(lenient); jsonWriter.setSerializeNulls(serializeNulls); return jsonWriter; } /** * Returns a new JSON reader configured for the settings on this Gson instance. + * + *

The following settings are considered: + *

    + *
  • {@link GsonBuilder#setLenient()}
  • + *
*/ public JsonReader newJsonReader(Reader reader) { JsonReader jsonReader = new JsonReader(reader); diff --git a/gson/src/test/java/com/google/gson/GsonTest.java b/gson/src/test/java/com/google/gson/GsonTest.java index c6cc4d5426..82c9740ab8 100644 --- a/gson/src/test/java/com/google/gson/GsonTest.java +++ b/gson/src/test/java/com/google/gson/GsonTest.java @@ -19,7 +19,10 @@ import com.google.gson.internal.Excluder; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +import com.google.gson.stream.MalformedJsonException; import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.text.DateFormat; @@ -82,4 +85,71 @@ private static final class TestTypeAdapter extends TypeAdapter { } @Override public Object read(JsonReader in) throws IOException { return null; } } + + public void testNewJsonWriter_Default() throws IOException { + StringWriter writer = new StringWriter(); + JsonWriter jsonWriter = new Gson().newJsonWriter(writer); + jsonWriter.beginObject(); + jsonWriter.name("test"); + jsonWriter.nullValue(); + jsonWriter.name(" Date: Mon, 1 Nov 2021 23:13:08 +0100 Subject: [PATCH 67/90] Improve TreeTypeAdapter thread-safety (#1976) * Improve TreeTypeAdapter thread-safety Gson claims to be thread-safe so TreeTypeAdapter.delegate() might be called by multiple threads. To guarantee that each thread sees a fully constructed `delegate`, the field has to be `volatile`. * Improve TreeTypeAdapter thread race comment --- .../java/com/google/gson/internal/bind/TreeTypeAdapter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/TreeTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/TreeTypeAdapter.java index a5c6c5dcda..03dfc32631 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TreeTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TreeTypeAdapter.java @@ -47,7 +47,7 @@ public final class TreeTypeAdapter extends TypeAdapter { private final GsonContextImpl context = new GsonContextImpl(); /** The delegate is lazily created because it may not be needed, and creating it may fail. */ - private TypeAdapter delegate; + private volatile TypeAdapter delegate; public TreeTypeAdapter(JsonSerializer serializer, JsonDeserializer deserializer, Gson gson, TypeToken typeToken, TypeAdapterFactory skipPast) { @@ -83,6 +83,7 @@ public TreeTypeAdapter(JsonSerializer serializer, JsonDeserializer deseria } private TypeAdapter delegate() { + // A race might lead to `delegate` being assigned by multiple threads but the last assignment will stick TypeAdapter d = delegate; return d != null ? d From cc505e1b9fa6e6e29466914a348315f73d326d27 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 7 Nov 2021 17:42:08 +0100 Subject: [PATCH 68/90] Convert codegen, metrics and proto to Maven submodules (#2008) * Convert codegen, metrics and proto to Maven submodules * Fix import order --- codegen/pom.xml | 172 ++++------------ extras/pom.xml | 2 - gson/pom.xml | 33 +++ metrics/pom.xml | 137 ++++--------- .../google/gson/metrics/ParseBenchmark.java | 34 +-- pom.xml | 32 +-- proto/pom.xml | 194 ++++-------------- .../{protobuf => proto}/annotations.proto | 0 proto/src/main/{protobuf => proto}/bag.proto | 2 + 9 files changed, 177 insertions(+), 429 deletions(-) rename proto/src/main/{protobuf => proto}/annotations.proto (100%) rename proto/src/main/{protobuf => proto}/bag.proto (99%) diff --git a/codegen/pom.xml b/codegen/pom.xml index cee611abd7..7b26e8afb4 100644 --- a/codegen/pom.xml +++ b/codegen/pom.xml @@ -1,170 +1,76 @@ 4.0.0 - com.google.code.gson + + com.google.code.gson + gson-parent + 2.9.0-SNAPSHOT + + gson-codegen - jar - 1.0-SNAPSHOT 2008 Gson Code Gen - - org.sonatype.oss - oss-parent - 7 - - http://code.google.com/p/google-gson/ Google Gson grab bag of utilities, type adapters, etc. - - UTF-8 - + - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt - - scm:svn:http://google-gson.googlecode.com/svn/trunk/extras - scm:svn:https://google-gson.googlecode.com/svn/trunk/extras - http://google-gson.codegoogle.com/svn/trunk/extras - - - Google Code Issue Tracking - http://code.google.com/p/google-gson/issues/list - + Google, Inc. - http://www.google.com + https://www.google.com + junit junit - 4.13.1 test - - - - release-sign-artifacts - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.4 - - - sign-artifacts - verify - - sign - - - - - - - - + - package org.apache.maven.plugins maven-compiler-plugin - 2.5.1 - - 1.6 - 1.6 - -proc:none - - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 + - package + default-compile + + -proc:none + + + + compile-project + compile - jar + compile - - - false - - - - - org.apache.maven.plugins - maven-source-plugin - 2.1.2 - - - attach-sources - verify - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.8.1 - - - attach-javadocs - - jar - - - - - - http://download.oracle.com/javase/1.5.0/docs/api/ - - true - public - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.9 - - true - true - - ../eclipse-ws/ - - - file:///${basedir}/../lib/gson-formatting-styles.xml - - - - - org.apache.maven.plugins - maven-release-plugin - - - -DenableCiProfile=true - https://google-gson.googlecode.com/svn/tags/ - + + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.0.0-M1 + + + true + + + + + Inderjeet Singh diff --git a/extras/pom.xml b/extras/pom.xml index 2cef36efd8..7215136fe2 100644 --- a/extras/pom.xml +++ b/extras/pom.xml @@ -7,8 +7,6 @@ gson-extras - jar - 2.9.0-SNAPSHOT 2008 Gson Extras Google Gson grab bag of utilities, type adapters, etc. diff --git a/gson/pom.xml b/gson/pom.xml index d9d37baa33..21e80e6987 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -31,6 +31,39 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + + + 9 + + 9 + + + + base-compile + + compile + + + + module-info.java + + + + + + + [1.5,9) + + 1.6 + 1.6 + + org.apache.maven.plugins maven-javadoc-plugin diff --git a/metrics/pom.xml b/metrics/pom.xml index 79641a5531..bd04d4f8c9 100644 --- a/metrics/pom.xml +++ b/metrics/pom.xml @@ -1,129 +1,64 @@ - 4.0.0 - com.google.code.gson + + com.google.code.gson + gson-parent + 2.9.0-SNAPSHOT + + gson-metrics - jar - 1.0-SNAPSHOT 2011 Gson Metrics - - org.sonatype.oss - oss-parent - 5 - - http://code.google.com/p/google-gson/ Performance Metrics for Google Gson library - - UTF-8 - + - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt - - scm:svn:http://google-gson.googlecode.com/svn/trunk/metrics - scm:svn:https://google-gson.googlecode.com/svn/trunk/metrics - http://google-gson.codegoogle.com/svn/trunk/metrics - - - Google Code Issue Tracking - http://code.google.com/p/google-gson/issues/list - + Google, Inc. - http://www.google.com + https://www.google.com + com.google.code.gson gson - 1.7.2-SNAPSHOT + ${project.parent.version} - com.google.code.caliper - caliper - 1.0-SNAPSHOT + com.fasterxml.jackson.core + jackson-databind + 2.13.0 - junit - junit - 4.13.1 - test + com.google.caliper + caliper + 0.5-rc1 + - package - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.8 - - true - true - ../eclipse-ws/ - - file:///${basedir}/../lib/gson-formatting-styles.xml - - - - - org.apache.maven.plugins - maven-release-plugin - 2.1 - - -DenableCiProfile=true - https://google-gson.googlecode.com/svn/tags/ - - - - org.apache.maven.plugins - maven-source-plugin - 2.1.2 - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.7 - - - attach-javadocs - - jar - - - - - - http://download.oracle.com/javase/1.5.0/docs/api/ - - true - public - - - + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.0.0-M1 + + + true + + + + + Inderjeet Singh diff --git a/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java index 68134372c4..a9e0f4e2f3 100644 --- a/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java +++ b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java @@ -16,6 +16,14 @@ package com.google.gson.metrics; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonFactoryBuilder; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.google.caliper.Param; import com.google.caliper.Runner; import com.google.caliper.SimpleBenchmark; @@ -34,11 +42,6 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; -import org.codehaus.jackson.JsonFactory; -import org.codehaus.jackson.annotate.JsonProperty; -import org.codehaus.jackson.map.DeserializationConfig; -import org.codehaus.jackson.map.ObjectMapper; -import org.codehaus.jackson.type.TypeReference; /** * Measure Gson and Jackson parsing and binding performance. @@ -139,6 +142,7 @@ interface Parser { } private static class GsonStreamParser implements Parser { + @Override public void parse(char[] data, Document document) throws Exception { com.google.gson.stream.JsonReader jsonReader = new com.google.gson.stream.JsonReader(new CharArrayReader(data)); @@ -186,6 +190,7 @@ private void readToken(com.google.gson.stream.JsonReader reader) throws IOExcept } private static class GsonSkipParser implements Parser { + @Override public void parse(char[] data, Document document) throws Exception { com.google.gson.stream.JsonReader jsonReader = new com.google.gson.stream.JsonReader(new CharArrayReader(data)); @@ -195,10 +200,10 @@ public void parse(char[] data, Document document) throws Exception { } private static class JacksonStreamParser implements Parser { + @Override public void parse(char[] data, Document document) throws Exception { - JsonFactory jsonFactory = new JsonFactory(); - org.codehaus.jackson.JsonParser jp = jsonFactory.createJsonParser(new CharArrayReader(data)); - jp.configure(org.codehaus.jackson.JsonParser.Feature.CANONICALIZE_FIELD_NAMES, false); + JsonFactory jsonFactory = new JsonFactoryBuilder().configure(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES, false).build(); + com.fasterxml.jackson.core.JsonParser jp = jsonFactory.createParser(new CharArrayReader(data)); int depth = 0; do { switch (jp.nextToken()) { @@ -227,8 +232,9 @@ public void parse(char[] data, Document document) throws Exception { } private static class GsonDomParser implements Parser { + @Override public void parse(char[] data, Document document) throws Exception { - new JsonParser().parse(new CharArrayReader(data)); + JsonParser.parseReader(new CharArrayReader(data)); } } @@ -237,20 +243,24 @@ private static class GsonBindParser implements Parser { .setDateFormat("EEE MMM dd HH:mm:ss Z yyyy") .create(); + @Override public void parse(char[] data, Document document) throws Exception { gson.fromJson(new CharArrayReader(data), document.gsonType); } } private static class JacksonBindParser implements Parser { - private static ObjectMapper mapper = new ObjectMapper(); + private static final ObjectMapper mapper; static { - mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); - mapper.configure(DeserializationConfig.Feature.AUTO_DETECT_FIELDS, true); + mapper = JsonMapper.builder() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(MapperFeature.AUTO_DETECT_FIELDS, true) + .build(); mapper.setDateFormat(new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy")); } + @Override public void parse(char[] data, Document document) throws Exception { mapper.readValue(new CharArrayReader(data), document.jacksonType); } diff --git a/pom.xml b/pom.xml index d0d45be9a1..5b2a3e1ff3 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,9 @@ gson extras + codegen + metrics + proto @@ -65,35 +68,6 @@ org.apache.maven.plugins maven-compiler-plugin 3.8.1 - - - default-compile - - - 9 - - 9 - - - - base-compile - - compile - - - - module-info.java - - - - - - - [1.5,9) - - 1.6 - 1.6 - org.apache.maven.plugins diff --git a/proto/pom.xml b/proto/pom.xml index afe55a1cf4..a5a4be91fc 100644 --- a/proto/pom.xml +++ b/proto/pom.xml @@ -4,82 +4,48 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.google.code.gson + + com.google.code.gson + gson-parent + 2.9.0-SNAPSHOT + + proto - jar - 0.6-SNAPSHOT Gson Protobuf Support Gson support for Protobufs - - UTF-8 - - - - local.repo - file repository to svn - file://${basedir}/../../mavenrepo - - - - - gson - http://google-gson.googlecode.com/svn/mavenrepo - - true - - - true - - - + - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt - - scm:svn:http://google-gson.googlecode.com/svn/trunk/proto - scm:svn:https://google-gson.googlecode.com/svn/trunk/proto - http://google-gson.codegoogle.com/svn/trunk/proto - - - Google Code Issue Tracking - http://code.google.com/p/google-gson/issues/list - - - com.google.code.gson gson - 2.8.9 - compile + ${project.parent.version} com.google.protobuf protobuf-java 4.0.0-rc-2 - compile - + com.google.guava guava 30.1.1-jre - compile junit junit - 4.13.2 test - + com.google.truth truth @@ -90,125 +56,49 @@ gson-proto + + + + kr.motd.maven + os-maven-plugin + 1.7.0 + + + - maven-antrun-plugin - 1.8 - - - compile-protoc - generate-sources - - - - - - - - - - - - - - - - - - - run - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.8 - - true - true - ../eclipse-ws - file:///${basedir}/../lib/gson-formatting-styles.xml - - - - org.apache.maven.plugins - maven-install-plugin - 2.5.2 - - - - org.apache.maven.plugins - maven-release-plugin - 2.1 + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 - -DenableCiProfile=true - https://google-gson.googlecode.com/svn/tags/ + com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier} - - - org.apache.maven.plugins - maven-source-plugin - 2.1.2 - attach-sources - jar + compile + test-compile - - org.apache.maven.plugins - maven-javadoc-plugin - 2.7 - - - attach-javadocs - - jar - - - - - - http://download.oracle.com/javase/1.5.0/docs/api/ - - true - public - - - - maven-assembly-plugin - - src/main/resources/assembly-descriptor.xml - proto-${project.version} - target/dist - target/assembly/work - - + + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.0.0-M1 + + + true + + + + + Inderjeet Singh diff --git a/proto/src/main/protobuf/annotations.proto b/proto/src/main/proto/annotations.proto similarity index 100% rename from proto/src/main/protobuf/annotations.proto rename to proto/src/main/proto/annotations.proto diff --git a/proto/src/main/protobuf/bag.proto b/proto/src/main/proto/bag.proto similarity index 99% rename from proto/src/main/protobuf/bag.proto rename to proto/src/main/proto/bag.proto index 48cc963977..3e4769e2a8 100644 --- a/proto/src/main/protobuf/bag.proto +++ b/proto/src/main/proto/bag.proto @@ -14,6 +14,8 @@ // limitations under the License. // +syntax = "proto2"; + package google.gson.protobuf.generated; option java_package = "com.google.gson.protobuf.generated"; From ca2ed748ba6e31c4a319ea6f2d2dc7048021f0a0 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 7 Nov 2021 17:43:49 +0100 Subject: [PATCH 69/90] Fix warnings (#2014) * Fix warnings * Address review feedback --- gson/pom.xml | 1 - .../main/java/com/google/gson/JsonArray.java | 5 ++++- .../java/com/google/gson/JsonObjectTest.java | 5 ++++- .../functional/DefaultTypeAdaptersTest.java | 1 - .../google/gson/internal/JavaVersionTest.java | 2 -- .../bind/DefaultDateTypeAdapterTest.java | 1 - .../internal/bind/util/ISO8601UtilsTest.java | 18 +++++++++--------- .../google/gson/stream/JsonReaderPathTest.java | 2 +- 8 files changed, 18 insertions(+), 17 deletions(-) diff --git a/gson/pom.xml b/gson/pom.xml index 21e80e6987..5357f2c556 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -68,7 +68,6 @@ org.apache.maven.plugins maven-javadoc-plugin - com.google.gson com.google.gson.internal:com.google.gson.internal.bind https://docs.oracle.com/javase/6/docs/api/ diff --git a/gson/src/main/java/com/google/gson/JsonArray.java b/gson/src/main/java/com/google/gson/JsonArray.java index 4b61a90969..f5ed713cc7 100644 --- a/gson/src/main/java/com/google/gson/JsonArray.java +++ b/gson/src/main/java/com/google/gson/JsonArray.java @@ -344,7 +344,10 @@ public byte getAsByte() { @Override public char getAsCharacter() { if (elements.size() == 1) { - return elements.get(0).getAsCharacter(); + JsonElement element = elements.get(0); + @SuppressWarnings("deprecation") + char result = element.getAsCharacter(); + return result; } throw new IllegalStateException(); } diff --git a/gson/src/test/java/com/google/gson/JsonObjectTest.java b/gson/src/test/java/com/google/gson/JsonObjectTest.java index 652b52885c..6f5274fcc8 100644 --- a/gson/src/test/java/com/google/gson/JsonObjectTest.java +++ b/gson/src/test/java/com/google/gson/JsonObjectTest.java @@ -104,7 +104,10 @@ public void testAddingCharacterProperties() throws Exception { JsonElement jsonElement = jsonObj.get(propertyName); assertNotNull(jsonElement); assertEquals(String.valueOf(value), jsonElement.getAsString()); - assertEquals(value, jsonElement.getAsCharacter()); + + @SuppressWarnings("deprecation") + char character = jsonElement.getAsCharacter(); + assertEquals(value, character); } /** diff --git a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java index e98e4a2960..136bde84da 100644 --- a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java @@ -39,7 +39,6 @@ import java.net.InetAddress; import java.net.URI; import java.net.URL; -import java.sql.Timestamp; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; diff --git a/gson/src/test/java/com/google/gson/internal/JavaVersionTest.java b/gson/src/test/java/com/google/gson/internal/JavaVersionTest.java index 4768c2dbc7..54b62862ca 100644 --- a/gson/src/test/java/com/google/gson/internal/JavaVersionTest.java +++ b/gson/src/test/java/com/google/gson/internal/JavaVersionTest.java @@ -19,8 +19,6 @@ import org.junit.Test; -import com.google.gson.internal.JavaVersion; - /** * Unit and functional tests for {@link JavaVersion} * diff --git a/gson/src/test/java/com/google/gson/internal/bind/DefaultDateTypeAdapterTest.java b/gson/src/test/java/com/google/gson/internal/bind/DefaultDateTypeAdapterTest.java index 2fdc64aeda..3d1ec7f794 100644 --- a/gson/src/test/java/com/google/gson/internal/bind/DefaultDateTypeAdapterTest.java +++ b/gson/src/test/java/com/google/gson/internal/bind/DefaultDateTypeAdapterTest.java @@ -27,7 +27,6 @@ import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.internal.JavaVersion; -import com.google.gson.internal.bind.DefaultDateTypeAdapter; import com.google.gson.internal.bind.DefaultDateTypeAdapter.DateType; import com.google.gson.reflect.TypeToken; diff --git a/gson/src/test/java/com/google/gson/internal/bind/util/ISO8601UtilsTest.java b/gson/src/test/java/com/google/gson/internal/bind/util/ISO8601UtilsTest.java index 1c6c69c056..bc0c9ec0f6 100644 --- a/gson/src/test/java/com/google/gson/internal/bind/util/ISO8601UtilsTest.java +++ b/gson/src/test/java/com/google/gson/internal/bind/util/ISO8601UtilsTest.java @@ -1,20 +1,16 @@ package com.google.gson.internal.bind.util; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; - +import org.junit.function.ThrowingRunnable; import java.text.ParseException; import java.text.ParsePosition; import java.util.*; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; public class ISO8601UtilsTest { - @Rule - public final ExpectedException exception = ExpectedException.none(); - private static TimeZone utcTimeZone() { return TimeZone.getTimeZone("UTC"); } @@ -87,8 +83,12 @@ public void testDateParseSpecialTimezone() throws ParseException { @Test public void testDateParseInvalidTime() throws ParseException { - String dateStr = "2018-06-25T61:60:62-03:00"; - exception.expect(ParseException.class); - ISO8601Utils.parse(dateStr, new ParsePosition(0)); + final String dateStr = "2018-06-25T61:60:62-03:00"; + assertThrows(ParseException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + ISO8601Utils.parse(dateStr, new ParsePosition(0)); + } + }); } } diff --git a/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java b/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java index 04614de4e5..ab802be1d7 100644 --- a/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java +++ b/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java @@ -307,7 +307,7 @@ public static List parameters() { assertEquals("$", reader.getPath()); } - enum Factory { + public enum Factory { STRING_READER { @Override public JsonReader create(String data) { return new JsonReader(new StringReader(data)); From 0d9f6b677ae67cbd749ebca817139041d1977831 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Nov 2021 08:43:47 -0800 Subject: [PATCH 70/90] Bump guava from 30.1.1-jre to 31.0.1-jre (#2016) Bumps [guava](https://github.com/google/guava) from 30.1.1-jre to 31.0.1-jre. - [Release notes](https://github.com/google/guava/releases) - [Commits](https://github.com/google/guava/commits) --- updated-dependencies: - dependency-name: com.google.guava:guava dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- proto/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto/pom.xml b/proto/pom.xml index a5a4be91fc..e3fba45cde 100644 --- a/proto/pom.xml +++ b/proto/pom.xml @@ -37,7 +37,7 @@ com.google.guava guava - 30.1.1-jre + 31.0.1-jre From b0595c595bd2c052cd05e0283bb37b67c02bd06f Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Tue, 9 Nov 2021 16:16:35 +0100 Subject: [PATCH 71/90] Fix failing to serialize Collection or Map with inaccessible constructor (#1902) * Remove UnsafeReflectionAccessor Revert #1218 Usage of sun.misc.Unsafe to change internal AccessibleObject.override field to suppress JPMS warnings goes against the intentions of the JPMS and does not work anymore in newer versions, see #1540. Therefore remove it and instead create a descriptive exception when making a member accessible fails. If necessary users can also still use `java` command line flags to open external modules. * Fix failing to serialize Collection or Map with inaccessible constructor Also remove tests which rely on Java implementation details. * Don't keep reference to access exception of ConstructorConstructor This also avoids a confusing stack trace, since the previously caught exception might have had a complete unrelated stack trace. * Remove Maven toolchain requirement * Address review feedback * Add back test for Security Manager --- gson/pom.xml | 19 ++- .../gson/internal/ConstructorConstructor.java | 66 ++++++---- .../bind/ReflectiveTypeAdapterFactory.java | 5 +- .../gson/internal/bind/TypeAdapters.java | 34 +++-- .../reflect/PreJava9ReflectionAccessor.java | 33 ----- .../internal/reflect/ReflectionAccessor.java | 54 -------- .../internal/reflect/ReflectionHelper.java | 66 ++++++++++ .../reflect/UnsafeReflectionAccessor.java | 86 ------------ .../functional/DefaultTypeAdaptersTest.java | 1 - .../com/google/gson/functional/EnumTest.java | 30 ++--- .../gson/functional/ReflectionAccessTest.java | 123 ++++++++++++++++++ .../functional/ThrowableFunctionalTest.java | 65 --------- .../bind/RecursiveTypesResolveTest.java | 15 --- .../reflect/UnsafeReflectionAccessorTest.java | 77 ----------- 14 files changed, 287 insertions(+), 387 deletions(-) delete mode 100644 gson/src/main/java/com/google/gson/internal/reflect/PreJava9ReflectionAccessor.java delete mode 100644 gson/src/main/java/com/google/gson/internal/reflect/ReflectionAccessor.java create mode 100644 gson/src/main/java/com/google/gson/internal/reflect/ReflectionHelper.java delete mode 100644 gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java create mode 100644 gson/src/test/java/com/google/gson/functional/ReflectionAccessTest.java delete mode 100644 gson/src/test/java/com/google/gson/functional/ThrowableFunctionalTest.java delete mode 100644 gson/src/test/java/com/google/gson/internal/reflect/UnsafeReflectionAccessorTest.java diff --git a/gson/pom.xml b/gson/pom.xml index 5357f2c556..8a0f6c3fb5 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -1,4 +1,6 @@ - + 4.0.0 @@ -64,6 +66,21 @@ 1.6 + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + + --illegal-access=deny + + org.apache.maven.plugins maven-javadoc-plugin diff --git a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java index 5fab460105..9ef0d39a1a 100644 --- a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java +++ b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java @@ -40,7 +40,7 @@ import com.google.gson.InstanceCreator; import com.google.gson.JsonIOException; -import com.google.gson.internal.reflect.ReflectionAccessor; +import com.google.gson.internal.reflect.ReflectionHelper; import com.google.gson.reflect.TypeToken; /** @@ -48,7 +48,6 @@ */ public final class ConstructorConstructor { private final Map> instanceCreators; - private final ReflectionAccessor accessor = ReflectionAccessor.getInstance(); public ConstructorConstructor(Map> instanceCreators) { this.instanceCreators = instanceCreators; @@ -97,33 +96,52 @@ public ObjectConstructor get(TypeToken typeToken) { } private ObjectConstructor newDefaultConstructor(Class rawType) { + final Constructor constructor; try { - final Constructor constructor = rawType.getDeclaredConstructor(); - if (!constructor.isAccessible()) { - accessor.makeAccessible(constructor); - } + constructor = rawType.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + return null; + } + + final String exceptionMessage = ReflectionHelper.tryMakeAccessible(constructor); + if (exceptionMessage != null) { + /* + * Create ObjectConstructor which throws exception. + * This keeps backward compatibility (compared to returning `null` which + * would then choose another way of creating object). + * And it supports types which are only serialized but not deserialized + * (compared to directly throwing exception here), e.g. when runtime type + * of object is inaccessible, but compile-time type is accessible. + */ return new ObjectConstructor() { - @SuppressWarnings("unchecked") // T is the same raw type as is requested - @Override public T construct() { - try { - Object[] args = null; - return (T) constructor.newInstance(args); - } catch (InstantiationException e) { - // TODO: JsonParseException ? - throw new RuntimeException("Failed to invoke " + constructor + " with no args", e); - } catch (InvocationTargetException e) { - // TODO: don't wrap if cause is unchecked! - // TODO: JsonParseException ? - throw new RuntimeException("Failed to invoke " + constructor + " with no args", - e.getTargetException()); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } + @Override + public T construct() { + // New exception is created every time to avoid keeping reference + // to exception with potentially long stack trace, causing a + // memory leak + throw new JsonIOException(exceptionMessage); } }; - } catch (NoSuchMethodException e) { - return null; } + + return new ObjectConstructor() { + @SuppressWarnings("unchecked") // T is the same raw type as is requested + @Override public T construct() { + try { + return (T) constructor.newInstance(); + } catch (InstantiationException e) { + // TODO: JsonParseException ? + throw new RuntimeException("Failed to invoke " + constructor + " with no args", e); + } catch (InvocationTargetException e) { + // TODO: don't wrap if cause is unchecked! + // TODO: JsonParseException ? + throw new RuntimeException("Failed to invoke " + constructor + " with no args", + e.getTargetException()); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + }; } /** diff --git a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java index 777e7dee35..21c049e23c 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java @@ -28,7 +28,7 @@ import com.google.gson.internal.Excluder; import com.google.gson.internal.ObjectConstructor; import com.google.gson.internal.Primitives; -import com.google.gson.internal.reflect.ReflectionAccessor; +import com.google.gson.internal.reflect.ReflectionHelper; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; @@ -50,7 +50,6 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory { private final FieldNamingStrategy fieldNamingPolicy; private final Excluder excluder; private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory; - private final ReflectionAccessor accessor = ReflectionAccessor.getInstance(); public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor, FieldNamingStrategy fieldNamingPolicy, Excluder excluder, @@ -156,7 +155,7 @@ private Map getBoundFields(Gson context, TypeToken type, if (!serialize && !deserialize) { continue; } - accessor.makeAccessible(field); + ReflectionHelper.makeAccessible(field); Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType()); List fieldNames = getFieldNames(field); BoundField previous = null; diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index 1b6b0118b6..81870bc9c4 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -17,6 +17,7 @@ package com.google.gson.internal.bind; import java.io.IOException; +import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.BigInteger; @@ -759,22 +760,31 @@ private static final class EnumTypeAdapter> extends TypeAdapte private final Map nameToConstant = new HashMap(); private final Map constantToName = new HashMap(); - public EnumTypeAdapter(Class classOfT) { + public EnumTypeAdapter(final Class classOfT) { try { - for (final Field field : classOfT.getDeclaredFields()) { - if (!field.isEnumConstant()) { - continue; - } - AccessController.doPrivileged(new PrivilegedAction() { - @Override public Void run() { - field.setAccessible(true); - return null; + // Uses reflection to find enum constants to work around name mismatches for obfuscated classes + // Reflection access might throw SecurityException, therefore run this in privileged context; + // should be acceptable because this only retrieves enum constants, but does not expose anything else + Field[] constantFields = AccessController.doPrivileged(new PrivilegedAction() { + @Override public Field[] run() { + Field[] fields = classOfT.getDeclaredFields(); + ArrayList constantFieldsList = new ArrayList(fields.length); + for (Field f : fields) { + if (f.isEnumConstant()) { + constantFieldsList.add(f); + } } - }); + + Field[] constantFields = constantFieldsList.toArray(new Field[0]); + AccessibleObject.setAccessible(constantFields, true); + return constantFields; + } + }); + for (Field constantField : constantFields) { @SuppressWarnings("unchecked") - T constant = (T)(field.get(null)); + T constant = (T)(constantField.get(null)); String name = constant.name(); - SerializedName annotation = field.getAnnotation(SerializedName.class); + SerializedName annotation = constantField.getAnnotation(SerializedName.class); if (annotation != null) { name = annotation.value(); for (String alternate : annotation.alternate()) { diff --git a/gson/src/main/java/com/google/gson/internal/reflect/PreJava9ReflectionAccessor.java b/gson/src/main/java/com/google/gson/internal/reflect/PreJava9ReflectionAccessor.java deleted file mode 100644 index 325274e224..0000000000 --- a/gson/src/main/java/com/google/gson/internal/reflect/PreJava9ReflectionAccessor.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2017 The Gson authors - * - * 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 com.google.gson.internal.reflect; - -import java.lang.reflect.AccessibleObject; - -/** - * A basic implementation of {@link ReflectionAccessor} which is suitable for Java 8 and below. - *

- * This implementation just calls {@link AccessibleObject#setAccessible(boolean) setAccessible(true)}, which worked - * fine before Java 9. - */ -final class PreJava9ReflectionAccessor extends ReflectionAccessor { - - /** {@inheritDoc} */ - @Override - public void makeAccessible(AccessibleObject ao) { - ao.setAccessible(true); - } -} diff --git a/gson/src/main/java/com/google/gson/internal/reflect/ReflectionAccessor.java b/gson/src/main/java/com/google/gson/internal/reflect/ReflectionAccessor.java deleted file mode 100644 index 6816feaf2b..0000000000 --- a/gson/src/main/java/com/google/gson/internal/reflect/ReflectionAccessor.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2017 The Gson authors - * - * 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 com.google.gson.internal.reflect; - -import java.lang.reflect.AccessibleObject; - -import com.google.gson.internal.JavaVersion; - -/** - * Provides a replacement for {@link AccessibleObject#setAccessible(boolean)}, which may be used to - * avoid reflective access issues appeared in Java 9, like {@link java.lang.reflect.InaccessibleObjectException} - * thrown or warnings like - *

- *   WARNING: An illegal reflective access operation has occurred
- *   WARNING: Illegal reflective access by ...
- * 
- *

- * Works both for Java 9 and earlier Java versions. - */ -public abstract class ReflectionAccessor { - - // the singleton instance, use getInstance() to obtain - private static final ReflectionAccessor instance = JavaVersion.getMajorJavaVersion() < 9 ? new PreJava9ReflectionAccessor() : new UnsafeReflectionAccessor(); - - /** - * Does the same as {@code ao.setAccessible(true)}, but never throws - * {@link java.lang.reflect.InaccessibleObjectException} - */ - public abstract void makeAccessible(AccessibleObject ao); - - /** - * Obtains a {@link ReflectionAccessor} instance suitable for the current Java version. - *

- * You may need one a reflective operation in your code throws {@link java.lang.reflect.InaccessibleObjectException}. - * In such a case, use {@link ReflectionAccessor#makeAccessible(AccessibleObject)} on a field, method or constructor - * (instead of basic {@link AccessibleObject#setAccessible(boolean)}). - */ - public static ReflectionAccessor getInstance() { - return instance; - } -} diff --git a/gson/src/main/java/com/google/gson/internal/reflect/ReflectionHelper.java b/gson/src/main/java/com/google/gson/internal/reflect/ReflectionHelper.java new file mode 100644 index 0000000000..a74de3025b --- /dev/null +++ b/gson/src/main/java/com/google/gson/internal/reflect/ReflectionHelper.java @@ -0,0 +1,66 @@ +package com.google.gson.internal.reflect; + +import com.google.gson.JsonIOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; + +public class ReflectionHelper { + private ReflectionHelper() { } + + /** + * Tries making the field accessible, wrapping any thrown exception in a + * {@link JsonIOException} with descriptive message. + * + * @param field field to make accessible + * @throws JsonIOException if making the field accessible fails + */ + public static void makeAccessible(Field field) throws JsonIOException { + try { + field.setAccessible(true); + } catch (Exception exception) { + throw new JsonIOException("Failed making field '" + field.getDeclaringClass().getName() + "#" + + field.getName() + "' accessible; either change its visibility or write a custom " + + "TypeAdapter for its declaring type", exception); + } + } + + /** + * Creates a string representation for a constructor. + * E.g.: {@code java.lang.String#String(char[], int, int)} + */ + private static String constructorToString(Constructor constructor) { + StringBuilder stringBuilder = new StringBuilder(constructor.getDeclaringClass().getName()) + .append('#') + .append(constructor.getDeclaringClass().getSimpleName()) + .append('('); + Class[] parameters = constructor.getParameterTypes(); + for (int i = 0; i < parameters.length; i++) { + if (i > 0) { + stringBuilder.append(", "); + } + stringBuilder.append(parameters[i].getSimpleName()); + } + + return stringBuilder.append(')').toString(); + } + + /** + * Tries making the constructor accessible, returning an exception message + * if this fails. + * + * @param constructor constructor to make accessible + * @return exception message; {@code null} if successful, non-{@code null} if + * unsuccessful + */ + public static String tryMakeAccessible(Constructor constructor) { + try { + constructor.setAccessible(true); + return null; + } catch (Exception exception) { + return "Failed making constructor '" + constructorToString(constructor) + "' accessible; " + + "either change its visibility or write a custom InstanceCreator or TypeAdapter for its declaring type: " + // Include the message since it might contain more detailed information + + exception.getMessage(); + } + } +} diff --git a/gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java b/gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java deleted file mode 100644 index b23d7babec..0000000000 --- a/gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2017 The Gson authors - * - * 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 com.google.gson.internal.reflect; - -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -import com.google.gson.JsonIOException; - -/** - * An implementation of {@link ReflectionAccessor} based on {@link Unsafe}. - *

- * NOTE: This implementation is designed for Java 9. Although it should work with earlier Java releases, it is better to - * use {@link PreJava9ReflectionAccessor} for them. - */ -@SuppressWarnings({"unchecked", "rawtypes"}) -final class UnsafeReflectionAccessor extends ReflectionAccessor { - - private static Class unsafeClass; - private final Object theUnsafe = getUnsafeInstance(); - private final Field overrideField = getOverrideField(); - - /** {@inheritDoc} */ - @Override - public void makeAccessible(AccessibleObject ao) { - boolean success = makeAccessibleWithUnsafe(ao); - if (!success) { - try { - // unsafe couldn't be found, so try using accessible anyway - ao.setAccessible(true); - } catch (SecurityException e) { - throw new JsonIOException("Gson couldn't modify fields for " + ao - + "\nand sun.misc.Unsafe not found.\nEither write a custom type adapter," - + " or make fields accessible, or include sun.misc.Unsafe.", e); - } - } - } - - // Visible for testing only - boolean makeAccessibleWithUnsafe(AccessibleObject ao) { - if (theUnsafe != null && overrideField != null) { - try { - Method method = unsafeClass.getMethod("objectFieldOffset", Field.class); - long overrideOffset = (Long) method.invoke(theUnsafe, overrideField); // long overrideOffset = theUnsafe.objectFieldOffset(overrideField); - Method putBooleanMethod = unsafeClass.getMethod("putBoolean", Object.class, long.class, boolean.class); - putBooleanMethod.invoke(theUnsafe, ao, overrideOffset, true); // theUnsafe.putBoolean(ao, overrideOffset, true); - return true; - } catch (Exception ignored) { // do nothing - } - } - return false; - } - - private static Object getUnsafeInstance() { - try { - unsafeClass = Class.forName("sun.misc.Unsafe"); - Field unsafeField = unsafeClass.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - return unsafeField.get(null); - } catch (Exception e) { - return null; - } - } - - private static Field getOverrideField() { - try { - return AccessibleObject.class.getDeclaredField("override"); - } catch (Exception e) { - return null; - } - } -} diff --git a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java index 136bde84da..1f9c75cd49 100644 --- a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java @@ -180,7 +180,6 @@ public void testNullSerialization() throws Exception { testNullSerializationAndDeserialization(Date.class); testNullSerializationAndDeserialization(GregorianCalendar.class); testNullSerializationAndDeserialization(Calendar.class); - testNullSerializationAndDeserialization(Enum.class); testNullSerializationAndDeserialization(Class.class); } diff --git a/gson/src/test/java/com/google/gson/functional/EnumTest.java b/gson/src/test/java/com/google/gson/functional/EnumTest.java index 66b855ebfc..8a1c6e12c6 100644 --- a/gson/src/test/java/com/google/gson/functional/EnumTest.java +++ b/gson/src/test/java/com/google/gson/functional/EnumTest.java @@ -16,12 +16,6 @@ package com.google.gson.functional; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collection; -import java.util.EnumSet; -import java.util.Set; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; @@ -34,7 +28,11 @@ import com.google.gson.annotations.SerializedName; import com.google.gson.common.MoreAsserts; import com.google.gson.reflect.TypeToken; - +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Set; import junit.framework.TestCase; /** * Functional tests for Java 5.0 enums. @@ -200,17 +198,17 @@ public enum Gender { } public void testEnumClassWithFields() { - assertEquals("\"RED\"", gson.toJson(Color.RED)); - assertEquals("red", gson.fromJson("RED", Color.class).value); + assertEquals("\"RED\"", gson.toJson(Color.RED)); + assertEquals("red", gson.fromJson("RED", Color.class).value); } public enum Color { - RED("red", 1), BLUE("blue", 2), GREEN("green", 3); - String value; - int index; - private Color(String value, int index) { - this.value = value; - this.index = index; - } + RED("red", 1), BLUE("blue", 2), GREEN("green", 3); + String value; + int index; + private Color(String value, int index) { + this.value = value; + this.index = index; + } } } diff --git a/gson/src/test/java/com/google/gson/functional/ReflectionAccessTest.java b/gson/src/test/java/com/google/gson/functional/ReflectionAccessTest.java new file mode 100644 index 0000000000..ece351240a --- /dev/null +++ b/gson/src/test/java/com/google/gson/functional/ReflectionAccessTest.java @@ -0,0 +1,123 @@ +package com.google.gson.functional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.lang.reflect.ReflectPermission; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.Permission; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.Test; + +public class ReflectionAccessTest { + @SuppressWarnings("unused") + private static class ClassWithPrivateMembers { + private String s; + + private ClassWithPrivateMembers() { + } + } + + private static Class loadClassWithDifferentClassLoader(Class c) throws Exception { + URL url = c.getProtectionDomain().getCodeSource().getLocation(); + URLClassLoader classLoader = new URLClassLoader(new URL[] { url }, null); + return classLoader.loadClass(c.getName()); + } + + @Test + public void testRestrictiveSecurityManager() throws Exception { + // Must use separate class loader, otherwise permission is not checked, see Class.getDeclaredFields() + Class clazz = loadClassWithDifferentClassLoader(ClassWithPrivateMembers.class); + + final Permission accessDeclaredMembers = new RuntimePermission("accessDeclaredMembers"); + final Permission suppressAccessChecks = new ReflectPermission("suppressAccessChecks"); + SecurityManager original = System.getSecurityManager(); + SecurityManager restrictiveManager = new SecurityManager() { + @Override + public void checkPermission(Permission perm) { + if (accessDeclaredMembers.equals(perm)) { + throw new SecurityException("Gson: no-member-access"); + } + if (suppressAccessChecks.equals(perm)) { + throw new SecurityException("Gson: no-suppress-access-check"); + } + } + }; + System.setSecurityManager(restrictiveManager); + + try { + Gson gson = new Gson(); + try { + // Getting reflection based adapter should fail + gson.getAdapter(clazz); + fail(); + } catch (SecurityException e) { + assertEquals("Gson: no-member-access", e.getMessage()); + } + + final AtomicBoolean wasReadCalled = new AtomicBoolean(false); + gson = new GsonBuilder() + .registerTypeAdapter(clazz, new TypeAdapter() { + @Override + public void write(JsonWriter out, Object value) throws IOException { + out.value("custom-write"); + } + + @Override + public Object read(JsonReader in) throws IOException { + in.skipValue(); + wasReadCalled.set(true); + return null; + }} + ) + .create(); + + assertEquals("\"custom-write\"", gson.toJson(null, clazz)); + assertNull(gson.fromJson("{}", clazz)); + assertTrue(wasReadCalled.get()); + } finally { + System.setSecurityManager(original); + } + } + + /** + * Test serializing an instance of a non-accessible internal class, but where + * Gson supports serializing one of its superinterfaces. + * + *

Here {@link Collections#emptyList()} is used which returns an instance + * of the internal class {@code java.util.Collections.EmptyList}. Gson should + * serialize the object as {@code List} despite the internal class not being + * accessible. + * + *

See https://github.com/google/gson/issues/1875 + */ + @Test + public void testSerializeInternalImplementationObject() { + Gson gson = new Gson(); + String json = gson.toJson(Collections.emptyList()); + assertEquals("[]", json); + + // But deserialization should fail + Class internalClass = Collections.emptyList().getClass(); + try { + gson.fromJson("{}", internalClass); + fail("Missing exception; test has to be run with `--illegal-access=deny`"); + } catch (JsonIOException expected) { + assertTrue(expected.getMessage().startsWith( + "Failed making constructor 'java.util.Collections$EmptyList#EmptyList()' accessible; " + + "either change its visibility or write a custom InstanceCreator or TypeAdapter for its declaring type" + )); + } + } +} diff --git a/gson/src/test/java/com/google/gson/functional/ThrowableFunctionalTest.java b/gson/src/test/java/com/google/gson/functional/ThrowableFunctionalTest.java deleted file mode 100644 index f6ae748a56..0000000000 --- a/gson/src/test/java/com/google/gson/functional/ThrowableFunctionalTest.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2014 Trymph Inc. -package com.google.gson.functional; - -import java.io.IOException; - -import junit.framework.TestCase; - -import com.google.gson.Gson; -import com.google.gson.annotations.SerializedName; - -@SuppressWarnings("serial") -public final class ThrowableFunctionalTest extends TestCase { - private final Gson gson = new Gson(); - - public void testExceptionWithoutCause() { - RuntimeException e = new RuntimeException("hello"); - String json = gson.toJson(e); - assertTrue(json.contains("hello")); - - e = gson.fromJson("{'detailMessage':'hello'}", RuntimeException.class); - assertEquals("hello", e.getMessage()); - } - - public void testExceptionWithCause() { - Exception e = new Exception("top level", new IOException("io error")); - String json = gson.toJson(e); - assertTrue(json.contains("{\"detailMessage\":\"top level\",\"cause\":{\"detailMessage\":\"io error\"")); - - e = gson.fromJson("{'detailMessage':'top level','cause':{'detailMessage':'io error'}}", Exception.class); - assertEquals("top level", e.getMessage()); - assertTrue(e.getCause() instanceof Throwable); // cause is not parameterized so type info is lost - assertEquals("io error", e.getCause().getMessage()); - } - - public void testSerializedNameOnExceptionFields() { - MyException e = new MyException(); - String json = gson.toJson(e); - assertTrue(json.contains("{\"my_custom_name\":\"myCustomMessageValue\"")); - } - - public void testErrorWithoutCause() { - OutOfMemoryError e = new OutOfMemoryError("hello"); - String json = gson.toJson(e); - assertTrue(json.contains("hello")); - - e = gson.fromJson("{'detailMessage':'hello'}", OutOfMemoryError.class); - assertEquals("hello", e.getMessage()); - } - - public void testErrornWithCause() { - Error e = new Error("top level", new IOException("io error")); - String json = gson.toJson(e); - assertTrue(json.contains("top level")); - assertTrue(json.contains("io error")); - - e = gson.fromJson("{'detailMessage':'top level','cause':{'detailMessage':'io error'}}", Error.class); - assertEquals("top level", e.getMessage()); - assertTrue(e.getCause() instanceof Throwable); // cause is not parameterized so type info is lost - assertEquals("io error", e.getCause().getMessage()); - } - - private static final class MyException extends Throwable { - @SerializedName("my_custom_name") String myCustomMessage = "myCustomMessageValue"; - } -} diff --git a/gson/src/test/java/com/google/gson/internal/bind/RecursiveTypesResolveTest.java b/gson/src/test/java/com/google/gson/internal/bind/RecursiveTypesResolveTest.java index a2260c373f..a2bece26e1 100644 --- a/gson/src/test/java/com/google/gson/internal/bind/RecursiveTypesResolveTest.java +++ b/gson/src/test/java/com/google/gson/internal/bind/RecursiveTypesResolveTest.java @@ -53,21 +53,6 @@ public void testRecursiveResolveSimple() { assertNotNull(adapter); } - /** - * Real-world samples, found in Issues #603 and #440. - */ - - public void testIssue603PrintStream() { - TypeAdapter adapter = new Gson().getAdapter(PrintStream.class); - assertNotNull(adapter); - } - - public void testIssue440WeakReference() throws Exception { - @SuppressWarnings("rawtypes") - TypeAdapter adapter = new Gson().getAdapter(WeakReference.class); - assertNotNull(adapter); - } - /** * Tests belows check the behaviour of the methods changed for the fix. */ diff --git a/gson/src/test/java/com/google/gson/internal/reflect/UnsafeReflectionAccessorTest.java b/gson/src/test/java/com/google/gson/internal/reflect/UnsafeReflectionAccessorTest.java deleted file mode 100644 index b330e66209..0000000000 --- a/gson/src/test/java/com/google/gson/internal/reflect/UnsafeReflectionAccessorTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2018 The Gson authors - * - * 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 com.google.gson.internal.reflect; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.lang.reflect.Field; -import java.security.Permission; - -import org.junit.Test; - -/** - * Unit tests for {@link UnsafeReflectionAccessor} - * - * @author Inderjeet Singh - */ -public class UnsafeReflectionAccessorTest { - - @Test - public void testMakeAccessibleWithUnsafe() throws Exception { - UnsafeReflectionAccessor accessor = new UnsafeReflectionAccessor(); - Field field = ClassWithPrivateFinalFields.class.getDeclaredField("a"); - try { - boolean success = accessor.makeAccessibleWithUnsafe(field); - assertTrue(success); - } catch (Exception e) { - fail("Unsafe didn't work on the JDK"); - } - } - - @Test - public void testMakeAccessibleWithRestrictiveSecurityManager() throws Exception { - final Permission accessDeclaredMembers = new RuntimePermission("accessDeclaredMembers"); - final SecurityManager original = System.getSecurityManager(); - SecurityManager restrictiveManager = new SecurityManager() { - @Override - public void checkPermission(Permission perm) { - if (accessDeclaredMembers.equals(perm)) { - throw new SecurityException("nope"); - } - } - }; - System.setSecurityManager(restrictiveManager); - - try { - UnsafeReflectionAccessor accessor = new UnsafeReflectionAccessor(); - Field field = ClassWithPrivateFinalFields.class.getDeclaredField("a"); - assertFalse("override field should have been inaccessible", accessor.makeAccessibleWithUnsafe(field)); - accessor.makeAccessible(field); - } finally { - System.setSecurityManager(original); - } - } - - @SuppressWarnings("unused") - private static final class ClassWithPrivateFinalFields { - private final String a; - public ClassWithPrivateFinalFields(String a) { - this.a = a; - } - } -} From 6e06bf0d89ad71f317c920cdaf9981a0508446d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Tue, 9 Nov 2021 10:08:21 -0800 Subject: [PATCH 72/90] Fix for an ArrayIndexOutOfBoundsException. The `fillBuffer` method changes `pos`, so it is incorrect to cache its previous value. --- .../java/com/google/gson/stream/JsonReader.java | 7 ++++--- .../com/google/gson/stream/JsonReaderTest.java | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/gson/src/main/java/com/google/gson/stream/JsonReader.java b/gson/src/main/java/com/google/gson/stream/JsonReader.java index 0e3acdb00a..59a9bf5377 100644 --- a/gson/src/main/java/com/google/gson/stream/JsonReader.java +++ b/gson/src/main/java/com/google/gson/stream/JsonReader.java @@ -228,13 +228,14 @@ public class JsonReader implements Closeable { /** True to accept non-spec compliant JSON */ private boolean lenient = false; + static final int BUFFER_SIZE = 1024; /** * Use a manual buffer to easily read and unread upcoming characters, and * also so we can create strings without an intermediate StringBuilder. * We decode literals directly out of this buffer, so it must be at least as * long as the longest token that can be reported as a number. */ - private final char[] buffer = new char[1024]; + private final char[] buffer = new char[BUFFER_SIZE]; private int pos = 0; private int limit = 0; @@ -1604,11 +1605,11 @@ private void consumeNonExecutePrefix() throws IOException { nextNonWhitespace(true); pos--; - int p = pos; - if (p + 5 > limit && !fillBuffer(5)) { + if (pos + 5 > limit && !fillBuffer(5)) { return; } + int p = pos; char[] buf = buffer; if(buf[p] != ')' || buf[p + 1] != ']' || buf[p + 2] != '}' || buf[p + 3] != '\'' || buf[p + 4] != '\n') { return; // not a security token! diff --git a/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java b/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java index e9f3aaa067..d305462479 100644 --- a/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java +++ b/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java @@ -1730,6 +1730,21 @@ public void testUnterminatedStringFailure() throws IOException { } } + /** + * Regression test for an issue with buffer filling and consumeNonExecutePrefix. + */ + public void testReadAcrossBuffers() throws IOException { + StringBuilder sb = new StringBuilder('#'); + for (int i = 0; i < JsonReader.BUFFER_SIZE - 3; i++) { + sb.append(' '); + } + sb.append("\n)]}'\n3"); + JsonReader reader = new JsonReader(reader(sb.toString())); + reader.setLenient(true); + JsonToken token = reader.peek(); + assertEquals(JsonToken.NUMBER, token); + } + private void assertDocument(String document, Object... expectations) throws IOException { JsonReader reader = new JsonReader(reader(document)); reader.setLenient(true); From 0313de8206ca6f68b31c9c01978ec9899677649e Mon Sep 17 00:00:00 2001 From: XinyuLiu5566 <59004176+XinyuLiu5566@users.noreply.github.com> Date: Mon, 15 Nov 2021 17:08:13 -0600 Subject: [PATCH 73/90] Some code suggestion from CodeGuru (#1988) * change %s to %d * secusity issue, add try-finally block Co-authored-by: liuxinyu --- .../gson/codegen/GeneratedTypeAdapterProcessor.java | 11 +++++++---- .../com/google/gson/protobuf/ProtoTypeAdapter.java | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapterProcessor.java b/codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapterProcessor.java index cd542bc353..c1a97bfa15 100644 --- a/codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapterProcessor.java +++ b/codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapterProcessor.java @@ -53,9 +53,12 @@ private void writeAdapter(TypeElement type) throws IOException { System.out.println("Generating type adapter: " + typeAdapterName + " in " + sourceFile.getName()); JavaWriter writer = new JavaWriter(sourceFile.openWriter()); - writer.addPackage(CodeGen.getPackage(type).getQualifiedName().toString()); - writer.beginType(typeAdapterName, "class", FINAL, null); - writer.endType(); - writer.close(); + try { + writer.addPackage(CodeGen.getPackage(type).getQualifiedName().toString()); + writer.beginType(typeAdapterName, "class", FINAL, null); + writer.endType(); + } finally { + writer.close(); + } } } diff --git a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java index c378685c8f..eb4a4f5458 100644 --- a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java +++ b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java @@ -389,7 +389,7 @@ private EnumValueDescriptor findValueByNameAndExtension(EnumDescriptor desc, EnumValueDescriptor fieldValue = desc.findValueByNumber(jsonElement.getAsInt()); if (fieldValue == null) { throw new IllegalArgumentException( - String.format("Unrecognized enum value: %s", jsonElement.getAsInt())); + String.format("Unrecognized enum value: %d", jsonElement.getAsInt())); } return fieldValue; } From 16b42ff5805074126c2e5484450c182773e408a2 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 22 Nov 2021 19:01:26 +0100 Subject: [PATCH 74/90] Update Caliper dependency; disable automatic result upload (#2019) --- metrics/pom.xml | 2 +- ...gOfPrimitivesDeserializationBenchmark.java | 16 +++-- .../CollectionsDeserializationBenchmark.java | 18 +++--- .../metrics/NonUploadingCaliperRunner.java | 21 +++++++ .../google/gson/metrics/ParseBenchmark.java | 58 ++++++++++++------ .../gson/metrics/SerializationBenchmark.java | 11 ++-- .../ParseBenchmarkData.zip | Bin 7 files changed, 80 insertions(+), 46 deletions(-) create mode 100644 metrics/src/main/java/com/google/gson/metrics/NonUploadingCaliperRunner.java rename metrics/src/main/{java/com/google/gson/metrics => resources}/ParseBenchmarkData.zip (100%) diff --git a/metrics/pom.xml b/metrics/pom.xml index bd04d4f8c9..6b49a59f6e 100644 --- a/metrics/pom.xml +++ b/metrics/pom.xml @@ -39,7 +39,7 @@ com.google.caliper caliper - 0.5-rc1 + 1.0-beta-3 diff --git a/metrics/src/main/java/com/google/gson/metrics/BagOfPrimitivesDeserializationBenchmark.java b/metrics/src/main/java/com/google/gson/metrics/BagOfPrimitivesDeserializationBenchmark.java index 8e6ea2b24b..80881dc8e6 100644 --- a/metrics/src/main/java/com/google/gson/metrics/BagOfPrimitivesDeserializationBenchmark.java +++ b/metrics/src/main/java/com/google/gson/metrics/BagOfPrimitivesDeserializationBenchmark.java @@ -15,15 +15,13 @@ */ package com.google.gson.metrics; +import com.google.caliper.BeforeExperiment; +import com.google.gson.Gson; +import com.google.gson.stream.JsonReader; import java.io.IOException; import java.io.StringReader; import java.lang.reflect.Field; -import com.google.caliper.Runner; -import com.google.caliper.SimpleBenchmark; -import com.google.gson.Gson; -import com.google.gson.stream.JsonReader; - /** * Caliper based micro benchmarks for Gson * @@ -31,17 +29,17 @@ * @author Jesse Wilson * @author Joel Leitch */ -public class BagOfPrimitivesDeserializationBenchmark extends SimpleBenchmark { +public class BagOfPrimitivesDeserializationBenchmark { private Gson gson; private String json; public static void main(String[] args) { - Runner.main(BagOfPrimitivesDeserializationBenchmark.class, args); + NonUploadingCaliperRunner.run(BagOfPrimitivesDeserializationBenchmark.class, args); } - @Override - protected void setUp() throws Exception { + @BeforeExperiment + void setUp() throws Exception { this.gson = new Gson(); BagOfPrimitives bag = new BagOfPrimitives(10L, 1, false, "foo"); this.json = gson.toJson(bag); diff --git a/metrics/src/main/java/com/google/gson/metrics/CollectionsDeserializationBenchmark.java b/metrics/src/main/java/com/google/gson/metrics/CollectionsDeserializationBenchmark.java index 09a5782ab5..57b5d630d2 100644 --- a/metrics/src/main/java/com/google/gson/metrics/CollectionsDeserializationBenchmark.java +++ b/metrics/src/main/java/com/google/gson/metrics/CollectionsDeserializationBenchmark.java @@ -15,6 +15,10 @@ */ package com.google.gson.metrics; +import com.google.caliper.BeforeExperiment; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; import java.io.IOException; import java.io.StringReader; import java.lang.reflect.Field; @@ -22,29 +26,23 @@ import java.util.ArrayList; import java.util.List; -import com.google.caliper.Runner; -import com.google.caliper.SimpleBenchmark; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; - /** * Caliper based micro benchmarks for Gson * * @author Inderjeet Singh */ -public class CollectionsDeserializationBenchmark extends SimpleBenchmark { +public class CollectionsDeserializationBenchmark { private static final Type LIST_TYPE = new TypeToken>(){}.getType(); private Gson gson; private String json; public static void main(String[] args) { - Runner.main(CollectionsDeserializationBenchmark.class, args); + NonUploadingCaliperRunner.run(CollectionsDeserializationBenchmark.class, args); } - @Override - protected void setUp() throws Exception { + @BeforeExperiment + void setUp() throws Exception { this.gson = new Gson(); List bags = new ArrayList(); for (int i = 0; i < 100; ++i) { diff --git a/metrics/src/main/java/com/google/gson/metrics/NonUploadingCaliperRunner.java b/metrics/src/main/java/com/google/gson/metrics/NonUploadingCaliperRunner.java new file mode 100644 index 0000000000..80633e1408 --- /dev/null +++ b/metrics/src/main/java/com/google/gson/metrics/NonUploadingCaliperRunner.java @@ -0,0 +1,21 @@ +package com.google.gson.metrics; + +import com.google.caliper.runner.CaliperMain; + +class NonUploadingCaliperRunner { + private static String[] concat(String first, String... others) { + if (others.length == 0) { + return new String[] { first }; + } else { + String[] result = new String[others.length + 1]; + result[0] = first; + System.arraycopy(others, 0, result, 1, others.length); + return result; + } + } + + public static void run(Class c, String[] args) { + // Disable result upload; Caliper uploads results to webapp by default, see https://github.com/google/caliper/issues/356 + CaliperMain.main(c, concat("-Cresults.upload.options.url=", args)); + } +} diff --git a/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java index a9e0f4e2f3..cc228e8715 100644 --- a/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java +++ b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java @@ -24,24 +24,27 @@ import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; +import com.google.caliper.BeforeExperiment; import com.google.caliper.Param; -import com.google.caliper.Runner; -import com.google.caliper.SimpleBenchmark; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParser; import com.google.gson.annotations.SerializedName; import com.google.gson.reflect.TypeToken; import java.io.CharArrayReader; +import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringWriter; import java.lang.reflect.Type; +import java.net.URL; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; +import java.util.Locale; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; /** * Measure Gson and Jackson parsing and binding performance. @@ -50,7 +53,7 @@ * That file contains Twitter feed data, which is representative of what * applications will be parsing. */ -public final class ParseBenchmark extends SimpleBenchmark { +public final class ParseBenchmark { @Param Document document; @Param Api api; @@ -105,8 +108,9 @@ private enum Api { private char[] text; private Parser parser; - @Override protected void setUp() throws Exception { - text = resourceToString("/" + document.name() + ".json").toCharArray(); + @BeforeExperiment + void setUp() throws Exception { + text = resourceToString(document.name() + ".json").toCharArray(); parser = api.newParser(); } @@ -116,25 +120,39 @@ public void timeParse(int reps) throws Exception { } } - private static String resourceToString(String path) throws Exception { - InputStream in = ParseBenchmark.class.getResourceAsStream(path); - if (in == null) { - throw new IllegalArgumentException("No such file: " + path); + private static File getResourceFile(String path) throws Exception { + URL url = ParseBenchmark.class.getResource(path); + if (url == null) { + throw new IllegalArgumentException("Resource " + path + " does not exist"); } + File file = new File(url.toURI()); + if (!file.isFile()) { + throw new IllegalArgumentException("Resource " + path + " is not a file"); + } + return file; + } + + private static String resourceToString(String fileName) throws Exception { + ZipFile zipFile = new ZipFile(getResourceFile("/ParseBenchmarkData.zip")); + try { + ZipEntry zipEntry = zipFile.getEntry(fileName); + Reader reader = new InputStreamReader(zipFile.getInputStream(zipEntry)); + char[] buffer = new char[8192]; + StringWriter writer = new StringWriter(); + int count; + while ((count = reader.read(buffer)) != -1) { + writer.write(buffer, 0, count); + } + reader.close(); + return writer.toString(); - Reader reader = new InputStreamReader(in, "UTF-8"); - char[] buffer = new char[8192]; - StringWriter writer = new StringWriter(); - int count; - while ((count = reader.read(buffer)) != -1) { - writer.write(buffer, 0, count); + } finally { + zipFile.close(); } - reader.close(); - return writer.toString(); } public static void main(String[] args) throws Exception { - Runner.main(ParseBenchmark.class, args); + NonUploadingCaliperRunner.run(ParseBenchmark.class, args); } interface Parser { @@ -257,7 +275,7 @@ private static class JacksonBindParser implements Parser { .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(MapperFeature.AUTO_DETECT_FIELDS, true) .build(); - mapper.setDateFormat(new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy")); + mapper.setDateFormat(new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", Locale.ENGLISH)); } @Override diff --git a/metrics/src/main/java/com/google/gson/metrics/SerializationBenchmark.java b/metrics/src/main/java/com/google/gson/metrics/SerializationBenchmark.java index 9cdf085e85..bf67a66b05 100644 --- a/metrics/src/main/java/com/google/gson/metrics/SerializationBenchmark.java +++ b/metrics/src/main/java/com/google/gson/metrics/SerializationBenchmark.java @@ -15,9 +15,8 @@ */ package com.google.gson.metrics; +import com.google.caliper.BeforeExperiment; import com.google.caliper.Param; -import com.google.caliper.Runner; -import com.google.caliper.SimpleBenchmark; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -28,7 +27,7 @@ * @author Jesse Wilson * @author Joel Leitch */ -public class SerializationBenchmark extends SimpleBenchmark { +public class SerializationBenchmark { private Gson gson; private BagOfPrimitives bag; @@ -36,11 +35,11 @@ public class SerializationBenchmark extends SimpleBenchmark { private boolean pretty; public static void main(String[] args) { - Runner.main(SerializationBenchmark.class, args); + NonUploadingCaliperRunner.run(SerializationBenchmark.class, args); } - @Override - protected void setUp() throws Exception { + @BeforeExperiment + void setUp() throws Exception { this.gson = pretty ? new GsonBuilder().setPrettyPrinting().create() : new Gson(); this.bag = new BagOfPrimitives(10L, 1, false, "foo"); } diff --git a/metrics/src/main/java/com/google/gson/metrics/ParseBenchmarkData.zip b/metrics/src/main/resources/ParseBenchmarkData.zip similarity index 100% rename from metrics/src/main/java/com/google/gson/metrics/ParseBenchmarkData.zip rename to metrics/src/main/resources/ParseBenchmarkData.zip From eaf9a0342d69b4cbbfb3644ce42e196453ce164a Mon Sep 17 00:00:00 2001 From: yixingz3 <88013471+yixingz3@users.noreply.github.com> Date: Sun, 28 Nov 2021 12:33:22 -0500 Subject: [PATCH 75/90] feat: added UPPER_CASE_WITH_UNDERSCORES in FieldNamingPolicy (#2024) --- .../com/google/gson/FieldNamingPolicy.java | 28 +++++++++++++++---- .../google/gson/FieldNamingPolicyTest.java | 3 +- .../gson/functional/FieldNamingTest.java | 9 ++++++ .../gson/functional/NamingPolicyTest.java | 16 +++++++++++ 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/gson/src/main/java/com/google/gson/FieldNamingPolicy.java b/gson/src/main/java/com/google/gson/FieldNamingPolicy.java index 16e7124f45..a4fa7c2715 100644 --- a/gson/src/main/java/com/google/gson/FieldNamingPolicy.java +++ b/gson/src/main/java/com/google/gson/FieldNamingPolicy.java @@ -44,7 +44,7 @@ public enum FieldNamingPolicy implements FieldNamingStrategy { * Using this naming policy with Gson will ensure that the first "letter" of the Java * field name is capitalized when serialized to its JSON form. * - *

Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

+ *

Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":

*
    *
  • someFieldName ---> SomeFieldName
  • *
  • _someFieldName ---> _SomeFieldName
  • @@ -61,7 +61,7 @@ public enum FieldNamingPolicy implements FieldNamingStrategy { * field name is capitalized when serialized to its JSON form and the words will be * separated by a space. * - *

    Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

    + *

    Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":

    *
      *
    • someFieldName ---> Some Field Name
    • *
    • _someFieldName ---> _Some Field Name
    • @@ -75,11 +75,29 @@ public enum FieldNamingPolicy implements FieldNamingStrategy { } }, + /** + * Using this naming policy with Gson will modify the Java Field name from its camel cased + * form to an upper case field name where each word is separated by an underscore (_). + * + *

      Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":

      + *
        + *
      • someFieldName ---> SOME_FIELD_NAME
      • + *
      • _someFieldName ---> _SOME_FIELD_NAME
      • + *
      • aStringField ---> A_STRING_FIELD
      • + *
      • aURL ---> A_U_R_L
      • + *
      + */ + UPPER_CASE_WITH_UNDERSCORES() { + @Override public String translateName(Field f) { + return separateCamelCase(f.getName(), '_').toUpperCase(Locale.ENGLISH); + } + }, + /** * Using this naming policy with Gson will modify the Java Field name from its camel cased * form to a lower case field name where each word is separated by an underscore (_). * - *

      Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

      + *

      Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":

      *
        *
      • someFieldName ---> some_field_name
      • *
      • _someFieldName ---> _some_field_name
      • @@ -97,7 +115,7 @@ public enum FieldNamingPolicy implements FieldNamingStrategy { * Using this naming policy with Gson will modify the Java Field name from its camel cased * form to a lower case field name where each word is separated by a dash (-). * - *

        Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

        + *

        Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":

        *
          *
        • someFieldName ---> some-field-name
        • *
        • _someFieldName ---> _some-field-name
        • @@ -120,7 +138,7 @@ public enum FieldNamingPolicy implements FieldNamingStrategy { * Using this naming policy with Gson will modify the Java Field name from its camel cased * form to a lower case field name where each word is separated by a dot (.). * - *

          Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

          + *

          Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":

          *
            *
          • someFieldName ---> some.field.name
          • *
          • _someFieldName ---> _some.field.name
          • diff --git a/gson/src/test/java/com/google/gson/FieldNamingPolicyTest.java b/gson/src/test/java/com/google/gson/FieldNamingPolicyTest.java index a62bae3aad..4d4c716b1e 100644 --- a/gson/src/test/java/com/google/gson/FieldNamingPolicyTest.java +++ b/gson/src/test/java/com/google/gson/FieldNamingPolicyTest.java @@ -67,7 +67,8 @@ class Dummy { FieldNamingPolicy[] policies = { FieldNamingPolicy.UPPER_CAMEL_CASE, - FieldNamingPolicy.UPPER_CAMEL_CASE_WITH_SPACES + FieldNamingPolicy.UPPER_CAMEL_CASE_WITH_SPACES, + FieldNamingPolicy.UPPER_CASE_WITH_UNDERSCORES, }; Field field = Dummy.class.getDeclaredField("i"); diff --git a/gson/src/test/java/com/google/gson/functional/FieldNamingTest.java b/gson/src/test/java/com/google/gson/functional/FieldNamingTest.java index 4e383ec83a..04ba7b7cbe 100644 --- a/gson/src/test/java/com/google/gson/functional/FieldNamingTest.java +++ b/gson/src/test/java/com/google/gson/functional/FieldNamingTest.java @@ -21,6 +21,7 @@ import static com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES; import static com.google.gson.FieldNamingPolicy.UPPER_CAMEL_CASE; import static com.google.gson.FieldNamingPolicy.UPPER_CAMEL_CASE_WITH_SPACES; +import static com.google.gson.FieldNamingPolicy.UPPER_CASE_WITH_UNDERSCORES; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; @@ -53,6 +54,14 @@ public void testUpperCamelCaseWithSpaces() { gson.toJson(new TestNames()).replace('\"', '\'')); } + public void testUpperCaseWithUnderscores() { + Gson gson = getGsonWithNamingPolicy(UPPER_CASE_WITH_UNDERSCORES); + assertEquals("{'LOWER_CAMEL':1,'UPPER_CAMEL':2,'_LOWER_CAMEL_LEADING_UNDERSCORE':3," + + "'__UPPER_CAMEL_LEADING_UNDERSCORE':4,'LOWER_WORDS':5,'U_P_P_E_R__W_O_R_D_S':6," + + "'annotatedName':7,'LOWER_ID':8,'_9':9}", + gson.toJson(new TestNames()).replace('\"', '\'')); + } + public void testLowerCaseWithUnderscores() { Gson gson = getGsonWithNamingPolicy(LOWER_CASE_WITH_UNDERSCORES); assertEquals("{'lower_camel':1,'upper_camel':2,'_lower_camel_leading_underscore':3," + diff --git a/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java b/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java index 5b1bba5beb..ab76e64918 100644 --- a/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java +++ b/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java @@ -141,6 +141,22 @@ public void testGsonWithUpperCamelCaseSpacesPolicyDeserialiation() { assertEquals("someValue", deserializedObject.someConstantStringInstanceField); } + public void testGsonWithUpperCaseUnderscorePolicySerialization() { + Gson gson = builder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CASE_WITH_UNDERSCORES) + .create(); + StringWrapper target = new StringWrapper("blah"); + assertEquals("{\"SOME_CONSTANT_STRING_INSTANCE_FIELD\":\"" + + target.someConstantStringInstanceField + "\"}", gson.toJson(target)); + } + + public void testGsonWithUpperCaseUnderscorePolicyDeserialiation() { + Gson gson = builder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CASE_WITH_UNDERSCORES) + .create(); + String target = "{\"SOME_CONSTANT_STRING_INSTANCE_FIELD\":\"someValue\"}"; + StringWrapper deserializedObject = gson.fromJson(target, StringWrapper.class); + assertEquals("someValue", deserializedObject.someConstantStringInstanceField); + } + public void testDeprecatedNamingStrategy() throws Exception { Gson gson = builder.setFieldNamingStrategy(new UpperCaseNamingStrategy()).create(); ClassWithDuplicateFields target = new ClassWithDuplicateFields(10); From 4a99674994359a93c515d7748bd5995e8a4fd539 Mon Sep 17 00:00:00 2001 From: DavidKorczynski Date: Thu, 2 Dec 2021 18:57:19 +0000 Subject: [PATCH 76/90] cifuzz: add integration (#2027) --- .github/workflows/cifuzz.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/cifuzz.yml diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 0000000000..29b86b329f --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,25 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'gson' + dry-run: false + language: jvm + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'gson' + fuzz-seconds: 600 + dry-run: false + - name: Upload Crash + uses: actions/upload-artifact@v1 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts From cdf8ab5213bf1c139029ecdd49cbe1e8ec103c02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Dec 2021 13:34:38 -0800 Subject: [PATCH 77/90] Bump bnd-maven-plugin from 6.0.0 to 6.1.0 (#2025) Bumps [bnd-maven-plugin](https://github.com/bndtools/bnd) from 6.0.0 to 6.1.0. - [Release notes](https://github.com/bndtools/bnd/releases) - [Changelog](https://github.com/bndtools/bnd/blob/master/docs/ADDING_RELEASE_DOCS.md) - [Commits](https://github.com/bndtools/bnd/compare/6.0.0...6.1.0) --- updated-dependencies: - dependency-name: biz.aQute.bnd:bnd-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gson/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gson/pom.xml b/gson/pom.xml index 8a0f6c3fb5..414d0832ed 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -94,7 +94,7 @@ biz.aQute.bnd bnd-maven-plugin - 6.0.0 + 6.1.0 From 4b3127f66907d066dd530876e7315a7a8d0821a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Dec 2021 14:36:39 -0800 Subject: [PATCH 78/90] Bump proguard-maven-plugin from 2.5.1 to 2.5.2 (#2034) Bumps [proguard-maven-plugin](https://github.com/wvengen/proguard-maven-plugin) from 2.5.1 to 2.5.2. - [Release notes](https://github.com/wvengen/proguard-maven-plugin/releases) - [Changelog](https://github.com/wvengen/proguard-maven-plugin/blob/master/CHANGELOG.md) - [Commits](https://github.com/wvengen/proguard-maven-plugin/commits) --- updated-dependencies: - dependency-name: com.github.wvengen:proguard-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gson/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gson/pom.xml b/gson/pom.xml index 414d0832ed..3bf2aff262 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -158,7 +158,7 @@ com.github.wvengen proguard-maven-plugin - 2.5.1 + 2.5.2 process-test-classes From 631046af28d2f28a6e5b490139d8fb39252e890e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Dec 2021 14:38:48 -0800 Subject: [PATCH 79/90] Bump maven-bundle-plugin from 5.1.2 to 5.1.3 (#2033) Bumps maven-bundle-plugin from 5.1.2 to 5.1.3. --- updated-dependencies: - dependency-name: org.apache.felix:maven-bundle-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5b2a3e1ff3..5e16a883b1 100644 --- a/pom.xml +++ b/pom.xml @@ -82,7 +82,7 @@ org.apache.felix maven-bundle-plugin - 5.1.2 + 5.1.3 true From 1dd150e86f8681c42f0dc4e7de01379d5fc9e58a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Dec 2021 09:37:56 -0800 Subject: [PATCH 80/90] Bump proguard-maven-plugin from 2.5.2 to 2.5.3 (#2037) Bumps [proguard-maven-plugin](https://github.com/wvengen/proguard-maven-plugin) from 2.5.2 to 2.5.3. - [Release notes](https://github.com/wvengen/proguard-maven-plugin/releases) - [Changelog](https://github.com/wvengen/proguard-maven-plugin/blob/master/CHANGELOG.md) - [Commits](https://github.com/wvengen/proguard-maven-plugin/commits) --- updated-dependencies: - dependency-name: com.github.wvengen:proguard-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gson/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gson/pom.xml b/gson/pom.xml index 3bf2aff262..43361e5eae 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -158,7 +158,7 @@ com.github.wvengen proguard-maven-plugin - 2.5.2 + 2.5.3 process-test-classes From 6dfbdc861ff7b5f027b8139671145ca67add541d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Dec 2021 09:38:18 -0800 Subject: [PATCH 81/90] Bump jackson-databind from 2.13.0 to 2.13.1 (#2036) Bumps [jackson-databind](https://github.com/FasterXML/jackson) from 2.13.0 to 2.13.1. - [Release notes](https://github.com/FasterXML/jackson/releases) - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- metrics/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metrics/pom.xml b/metrics/pom.xml index 6b49a59f6e..613568fa82 100644 --- a/metrics/pom.xml +++ b/metrics/pom.xml @@ -34,7 +34,7 @@ com.fasterxml.jackson.core jackson-databind - 2.13.0 + 2.13.1 com.google.caliper From 6ffcdf302939ad405abdfb468218b8caafc46e9c Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 27 Dec 2021 00:30:21 +0100 Subject: [PATCH 82/90] Fix Javadoc warnings and errors (#2040) --- .../main/java/com/google/gson/interceptors/Intercept.java | 6 +++--- gson/src/main/java/com/google/gson/Gson.java | 6 +++--- gson/src/main/java/com/google/gson/GsonBuilder.java | 6 +++--- gson/src/main/java/com/google/gson/JsonSerializer.java | 8 ++++---- gson/src/main/java/com/google/gson/ToNumberStrategy.java | 1 - .../src/main/java/com/google/gson/annotations/Expose.java | 4 ++-- gson/src/main/java/com/google/gson/stream/JsonReader.java | 2 +- .../java/com/google/gson/protobuf/ProtoTypeAdapter.java | 7 +++---- 8 files changed, 19 insertions(+), 21 deletions(-) diff --git a/extras/src/main/java/com/google/gson/interceptors/Intercept.java b/extras/src/main/java/com/google/gson/interceptors/Intercept.java index 0c4e9043f6..fef29cbf0b 100644 --- a/extras/src/main/java/com/google/gson/interceptors/Intercept.java +++ b/extras/src/main/java/com/google/gson/interceptors/Intercept.java @@ -28,8 +28,8 @@ * after it has been deserialized from Json. * Here is an example of how this annotation is used: *

            Here is an example of how this annotation is used: - *

            - * @Intercept(postDeserialize=UserValidator.class)
            + * 
            + * @Intercept(postDeserialize=UserValidator.class)
              * public class User {
              *   String name;
              *   String password;
            @@ -47,7 +47,7 @@
              *     }
              *   }
              * }
            - * 

            + *
            * * @author Inderjeet Singh */ diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 2015b4cafe..ce4517a31e 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -76,7 +76,7 @@ * MyType target = new MyType(); * String json = gson.toJson(target); // serializes target to Json * MyType target2 = gson.fromJson(json, MyType.class); // deserializes json into target2 - *

            + * * *

            If the object that your are serializing/deserializing is a {@code ParameterizedType} * (i.e. contains at least one type parameter and may be an array) then you must use the @@ -91,7 +91,7 @@ * Gson gson = new Gson(); * String json = gson.toJson(target, listType); * List<String> target2 = gson.fromJson(json, listType); - *

            + * * *

            See the Gson User Guide * for a more complete set of examples.

            @@ -548,7 +548,7 @@ public TypeAdapter getAdapter(TypeToken type) { * read or written. * @param skipPast The type adapter factory that needs to be skipped while searching for * a matching type adapter. In most cases, you should just pass this (the type adapter - * factory from where {@link #getDelegateAdapter} method is being invoked). + * factory from where {@code getDelegateAdapter} method is being invoked). * @param type Type for which the delegate adapter is being searched for. * * @since 2.2 diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index b798604ab6..fa2bb9266a 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -61,7 +61,7 @@ * .setPrettyPrinting() * .setVersion(1.0) * .create(); - *

            + * * *

            NOTES: *

              @@ -69,8 +69,7 @@ *
            • The default serialization of {@link Date} and its subclasses in Gson does * not contain time-zone information. So, if you are using date/time instances, * use {@code GsonBuilder} and its {@code setDateFormat} methods.
            • - *
            - *

            + *
          * * @author Inderjeet Singh * @author Joel Leitch @@ -251,6 +250,7 @@ public GsonBuilder serializeNulls() { * original.put(new Point(8, 8), "b"); * System.out.println(gson.toJson(original, type)); * } + * * * The JSON output would look as follows: *
             {@code
          diff --git a/gson/src/main/java/com/google/gson/JsonSerializer.java b/gson/src/main/java/com/google/gson/JsonSerializer.java
          index a605003364..19eaf17d3f 100644
          --- a/gson/src/main/java/com/google/gson/JsonSerializer.java
          +++ b/gson/src/main/java/com/google/gson/JsonSerializer.java
          @@ -26,7 +26,7 @@
            * 

          Let us look at example where defining a serializer will be useful. The {@code Id} class * defined below has two fields: {@code clazz} and {@code value}.

          * - *

          + * 
            * public class Id<T> {
            *   private final Class<T> clazz;
            *   private final long value;
          @@ -40,20 +40,20 @@
            *     return value;
            *   }
            * }
          - * 

          + *
          * *

          The default serialization of {@code Id(com.foo.MyObject.class, 20L)} will be * {"clazz":com.foo.MyObject,"value":20}. Suppose, you just want the output to be * the value instead, which is {@code 20} in this case. You can achieve that by writing a custom * serializer:

          * - *

          + * 
            * class IdSerializer implements JsonSerializer<Id>() {
            *   public JsonElement serialize(Id id, Type typeOfId, JsonSerializationContext context) {
            *     return new JsonPrimitive(id.getValue());
            *   }
            * }
          - * 

          + *
          * *

          You will also need to register {@code IdSerializer} with Gson as follows:

          *
          diff --git a/gson/src/main/java/com/google/gson/ToNumberStrategy.java b/gson/src/main/java/com/google/gson/ToNumberStrategy.java
          index db42a4efe6..3cd84fa5a8 100644
          --- a/gson/src/main/java/com/google/gson/ToNumberStrategy.java
          +++ b/gson/src/main/java/com/google/gson/ToNumberStrategy.java
          @@ -65,7 +65,6 @@ public interface ToNumberStrategy {
              *
              * @param in JSON reader to read a number from
              * @return number read from the JSON reader.
          -   * @throws IOException
              */
             public Number readNumber(JsonReader in) throws IOException;
           }
          diff --git a/gson/src/main/java/com/google/gson/annotations/Expose.java b/gson/src/main/java/com/google/gson/annotations/Expose.java
          index 19a9297040..966460dbc6 100644
          --- a/gson/src/main/java/com/google/gson/annotations/Expose.java
          +++ b/gson/src/main/java/com/google/gson/annotations/Expose.java
          @@ -32,14 +32,14 @@
            * method.

          * *

          Here is an example of how this annotation is meant to be used: - *

          + * 
            * public class User {
            *   @Expose private String firstName;
            *   @Expose(serialize = false) private String lastName;
            *   @Expose (serialize = false, deserialize = false) private String emailAddress;
            *   private String password;
            * }
          - * 

          + *
          * If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()} * methods will use the {@code password} field along-with {@code firstName}, {@code lastName}, * and {@code emailAddress} for serialization and deserialization. However, if you created Gson diff --git a/gson/src/main/java/com/google/gson/stream/JsonReader.java b/gson/src/main/java/com/google/gson/stream/JsonReader.java index 59a9bf5377..a8cb22aa32 100644 --- a/gson/src/main/java/com/google/gson/stream/JsonReader.java +++ b/gson/src/main/java/com/google/gson/stream/JsonReader.java @@ -170,7 +170,7 @@ * precision loss, extremely large values should be written and read as strings * in JSON. * - *

          Non-Execute Prefix

          + *

          Non-Execute Prefix

          * Web servers that serve private data using JSON may be vulnerable to
          Cross-site * request forgery attacks. In such an attack, a malicious site gains access diff --git a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java index eb4a4f5458..d588d82404 100644 --- a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java +++ b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java @@ -37,7 +37,6 @@ import com.google.protobuf.DynamicMessage; import com.google.protobuf.Extension; import com.google.protobuf.Message; - import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -76,7 +75,7 @@ public class ProtoTypeAdapter /** * Determines how enum values should be serialized. */ - public static enum EnumSerialization { + public enum EnumSerialization { /** * Serializes and deserializes enum values using their number. When this is used, custom * value names set on enums are ignored. @@ -117,12 +116,12 @@ public Builder setEnumSerialization(EnumSerialization enumSerialization) { * For example, if you use the following parameters: {@link CaseFormat#LOWER_UNDERSCORE}, * {@link CaseFormat#LOWER_CAMEL}, the following conversion will occur: * - *
          +     * 
          {@code
                * PROTO     <->  JSON
                * my_field       myField
                * foo            foo
                * n__id_ct       nIdCt
          -     * 
          + * }
          */ public Builder setFieldNameSerializationFormat(CaseFormat fromFieldNameFormat, CaseFormat toFieldNameFormat) { From abd2191b0e3863aa227b19fd3ebc6820cbcec058 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 27 Dec 2021 19:17:41 +0100 Subject: [PATCH 83/90] Add READMEs to Maven modules (#2039) * Add READMEs to Maven modules * Address feedback --- codegen/README.md | 5 +++++ examples/android-proguard-example/README.md | 9 +++++++++ extras/README.md | 6 ++++++ gson/README | 7 ------- gson/README.md | 4 ++++ metrics/README.md | 3 +++ proto/README.md | 7 +++++++ 7 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 codegen/README.md create mode 100644 examples/android-proguard-example/README.md create mode 100644 extras/README.md delete mode 100644 gson/README create mode 100644 gson/README.md create mode 100644 metrics/README.md create mode 100644 proto/README.md diff --git a/codegen/README.md b/codegen/README.md new file mode 100644 index 0000000000..adee425a23 --- /dev/null +++ b/codegen/README.md @@ -0,0 +1,5 @@ +# gson-codegen + +This Maven module contains the source code for automatically generating Gson type adapters. + +:warning: This module is currently non-functional and might be removed in the future. diff --git a/examples/android-proguard-example/README.md b/examples/android-proguard-example/README.md new file mode 100644 index 0000000000..bc4b2e758f --- /dev/null +++ b/examples/android-proguard-example/README.md @@ -0,0 +1,9 @@ +# android-proguard-example + +Example Android project showing how to properly configure [ProGuard](https://www.guardsquare.com/proguard). +ProGuard is a tool for 'shrinking' and obfuscating compiled classes. It can rename methods and fields, +or remove them if they appear to be unused. This can cause issues for Gson which uses Java reflection to +access the fields of a class. It is necessary to configure ProGuard to make sure that Gson works correctly. + +Also have a look at the [ProGuard manual](https://www.guardsquare.com/manual/configuration/usage#keepoverview) +for more details on how ProGuard can be configured. diff --git a/extras/README.md b/extras/README.md new file mode 100644 index 0000000000..41447726e2 --- /dev/null +++ b/extras/README.md @@ -0,0 +1,6 @@ +# extras + +This Maven module contains the source code for supplementary Gson features which +are not included by default. + +The artifacts created by this module are currently not deployed to Maven Central. diff --git a/gson/README b/gson/README deleted file mode 100644 index a925a5cd0c..0000000000 --- a/gson/README +++ /dev/null @@ -1,7 +0,0 @@ -Gson is a Java library that can be used to convert Java Objects into their -JSON representation. It can also be used to convert a JSON string to an -equivalent Java object. Gson can work with arbitrary Java objects including -pre-existing objects that you do not have source-code of. - -Complete Gson documentation is available at its project page -https://github.com/google/gson diff --git a/gson/README.md b/gson/README.md new file mode 100644 index 0000000000..75ec9fc92a --- /dev/null +++ b/gson/README.md @@ -0,0 +1,4 @@ +# gson + +This Maven module contains the Gson source code. The artifacts created by this module +are deployed to Maven Central under the coordinates `com.google.code.gson:gson`. diff --git a/metrics/README.md b/metrics/README.md new file mode 100644 index 0000000000..8c95485de2 --- /dev/null +++ b/metrics/README.md @@ -0,0 +1,3 @@ +# metrics + +This Maven module contains the source code for running internal benchmark tests against Gson. diff --git a/proto/README.md b/proto/README.md new file mode 100644 index 0000000000..c6f7906a68 --- /dev/null +++ b/proto/README.md @@ -0,0 +1,7 @@ +# proto + +This Maven module contains the source code for a JSON serializer and deserializer for +[Protocol Buffers (protobuf)](https://developers.google.com/protocol-buffers/docs/javatutorial) +messages. + +The artifacts created by this module are currently not deployed to Maven Central. From 97938283a77707cdf231a7ebdcbee937f289ad31 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Tue, 28 Dec 2021 18:56:49 +0100 Subject: [PATCH 84/90] Remove explicit ProGuard plugin dependencies (#2041) Explicitly specifying dependencies only seems to be necessary when using `` config element to override version (and even that might not be necessary; only adding explicit dependencies might suffice). However, when omitting it, plugin uses a recent ProGuard version on its own. --- gson/pom.xml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/gson/pom.xml b/gson/pom.xml index 43361e5eae..31ea881f6b 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -12,10 +12,6 @@ gson Gson - - 7.1.1 - - Apache-2.0 @@ -168,7 +164,6 @@ - ${proguardVersion} true test-classes-obfuscated-injar test-classes-obfuscated-outjar @@ -179,20 +174,6 @@ ${java.home}/jmods/java.base.jmod - - - com.guardsquare - proguard-core - ${proguardVersion} - runtime - - - com.guardsquare - proguard-base - ${proguardVersion} - runtime - - maven-resources-plugin From 615c8835d309e1be512dd98809b48332ce70250d Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Fri, 31 Dec 2021 00:08:18 +0100 Subject: [PATCH 85/90] Add `GsonBuilder.disableJdkUnsafe()` (#1904) * Add GsonBuilder.disableJdkUnsafe() * Address review feedback --- .../gson/graph/GraphAdapterBuilder.java | 5 +- gson/src/main/java/com/google/gson/Gson.java | 9 +++- .../java/com/google/gson/GsonBuilder.java | 25 +++++++++- .../gson/internal/ConstructorConstructor.java | 50 ++++++++++++------- .../google/gson/internal/UnsafeAllocator.java | 3 +- .../java/com/google/gson/GsonBuilderTest.java | 23 +++++++++ .../test/java/com/google/gson/GsonTest.java | 4 +- 7 files changed, 94 insertions(+), 25 deletions(-) diff --git a/extras/src/main/java/com/google/gson/graph/GraphAdapterBuilder.java b/extras/src/main/java/com/google/gson/graph/GraphAdapterBuilder.java index cd8ea00f47..90ee595782 100644 --- a/extras/src/main/java/com/google/gson/graph/GraphAdapterBuilder.java +++ b/extras/src/main/java/com/google/gson/graph/GraphAdapterBuilder.java @@ -47,11 +47,12 @@ public final class GraphAdapterBuilder { public GraphAdapterBuilder() { this.instanceCreators = new HashMap>(); - this.constructorConstructor = new ConstructorConstructor(instanceCreators); + this.constructorConstructor = new ConstructorConstructor(instanceCreators, true); } public GraphAdapterBuilder addType(Type type) { final ObjectConstructor objectConstructor = constructorConstructor.get(TypeToken.get(type)); InstanceCreator instanceCreator = new InstanceCreator() { + @Override public Object createInstance(Type type) { return objectConstructor.construct(); } @@ -83,6 +84,7 @@ static class Factory implements TypeAdapterFactory, InstanceCreator { this.instanceCreators = instanceCreators; } + @Override public TypeAdapter create(Gson gson, TypeToken type) { if (!instanceCreators.containsKey(type.getType())) { return null; @@ -212,6 +214,7 @@ public TypeAdapter create(Gson gson, TypeToken type) { * that is only when we've called back into Gson to deserialize a tree. */ @SuppressWarnings("unchecked") + @Override public Object createInstance(Type type) { Graph graph = graphThreadLocal.get(); if (graph == null || graph.nextCreate == null) { diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index ce4517a31e..106bc75dc2 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -110,6 +110,7 @@ public final class Gson { static final boolean DEFAULT_SERIALIZE_NULLS = false; static final boolean DEFAULT_COMPLEX_MAP_KEYS = false; static final boolean DEFAULT_SPECIALIZE_FLOAT_VALUES = false; + static final boolean DEFAULT_USE_JDK_UNSAFE = true; private static final TypeToken NULL_KEY_SURROGATE = TypeToken.get(Object.class); private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n"; @@ -141,6 +142,7 @@ public final class Gson { final boolean prettyPrinting; final boolean lenient; final boolean serializeSpecialFloatingPointValues; + final boolean useJdkUnsafe; final String datePattern; final int dateStyle; final int timeStyle; @@ -189,6 +191,7 @@ public Gson() { Collections.>emptyMap(), DEFAULT_SERIALIZE_NULLS, DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML, DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES, + DEFAULT_USE_JDK_UNSAFE, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), ToNumberPolicy.DOUBLE, ToNumberPolicy.LAZILY_PARSED_NUMBER); @@ -198,15 +201,16 @@ public Gson() { Map> instanceCreators, boolean serializeNulls, boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe, boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues, + boolean useJdkUnsafe, LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle, int timeStyle, List builderFactories, List builderHierarchyFactories, List factoriesToBeAdded, - ToNumberStrategy objectToNumberStrategy, ToNumberStrategy numberToNumberStrategy) { + ToNumberStrategy objectToNumberStrategy, ToNumberStrategy numberToNumberStrategy) { this.excluder = excluder; this.fieldNamingStrategy = fieldNamingStrategy; this.instanceCreators = instanceCreators; - this.constructorConstructor = new ConstructorConstructor(instanceCreators); + this.constructorConstructor = new ConstructorConstructor(instanceCreators, useJdkUnsafe); this.serializeNulls = serializeNulls; this.complexMapKeySerialization = complexMapKeySerialization; this.generateNonExecutableJson = generateNonExecutableGson; @@ -214,6 +218,7 @@ public Gson() { this.prettyPrinting = prettyPrinting; this.lenient = lenient; this.serializeSpecialFloatingPointValues = serializeSpecialFloatingPointValues; + this.useJdkUnsafe = useJdkUnsafe; this.longSerializationPolicy = longSerializationPolicy; this.datePattern = datePattern; this.dateStyle = dateStyle; diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index fa2bb9266a..3fb9e41014 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -41,6 +41,7 @@ import static com.google.gson.Gson.DEFAULT_PRETTY_PRINT; import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS; import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES; +import static com.google.gson.Gson.DEFAULT_USE_JDK_UNSAFE; /** *

          Use this builder to construct a {@link Gson} instance when you need to set configuration @@ -94,6 +95,7 @@ public final class GsonBuilder { private boolean prettyPrinting = DEFAULT_PRETTY_PRINT; private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE; private boolean lenient = DEFAULT_LENIENT; + private boolean useJdkUnsafe = DEFAULT_USE_JDK_UNSAFE; private ToNumberStrategy objectToNumberStrategy = ToNumberPolicy.DOUBLE; private ToNumberStrategy numberToNumberStrategy = ToNumberPolicy.LAZILY_PARSED_NUMBER; @@ -129,6 +131,7 @@ public GsonBuilder() { this.timeStyle = gson.timeStyle; this.factories.addAll(gson.builderFactories); this.hierarchyFactories.addAll(gson.builderHierarchyFactories); + this.useJdkUnsafe = gson.useJdkUnsafe; this.objectToNumberStrategy = gson.objectToNumberStrategy; this.numberToNumberStrategy = gson.numberToNumberStrategy; } @@ -606,6 +609,26 @@ public GsonBuilder serializeSpecialFloatingPointValues() { return this; } + /** + * Disables usage of JDK's {@code sun.misc.Unsafe}. + * + *

          By default Gson uses {@code Unsafe} to create instances of classes which don't have + * a no-args constructor. However, {@code Unsafe} might not be available for all Java + * runtimes. For example Android does not provide {@code Unsafe}, or only with limited + * functionality. Additionally {@code Unsafe} creates instances without executing any + * constructor or initializer block, or performing initialization of field values. This can + * lead to surprising and difficult to debug errors. + * Therefore, to get reliable behavior regardless of which runtime is used, and to detect + * classes which cannot be deserialized in an early stage of development, this method allows + * disabling usage of {@code Unsafe}. + * + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + public GsonBuilder disableJdkUnsafe() { + this.useJdkUnsafe = false; + return this; + } + /** * Creates a {@link Gson} instance based on the current configuration. This method is free of * side-effects to this {@code GsonBuilder} instance and hence can be called multiple times. @@ -626,7 +649,7 @@ public Gson create() { return new Gson(excluder, fieldNamingPolicy, instanceCreators, serializeNulls, complexMapKeySerialization, generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient, - serializeSpecialFloatingPointValues, longSerializationPolicy, + serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy, datePattern, dateStyle, timeStyle, this.factories, this.hierarchyFactories, factories, objectToNumberStrategy, numberToNumberStrategy); } diff --git a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java index 9ef0d39a1a..aa1e3ff61b 100644 --- a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java +++ b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java @@ -48,9 +48,11 @@ */ public final class ConstructorConstructor { private final Map> instanceCreators; + private final boolean useJdkUnsafe; - public ConstructorConstructor(Map> instanceCreators) { + public ConstructorConstructor(Map> instanceCreators, boolean useJdkUnsafe) { this.instanceCreators = instanceCreators; + this.useJdkUnsafe = useJdkUnsafe; } public ObjectConstructor get(TypeToken typeToken) { @@ -92,7 +94,7 @@ public ObjectConstructor get(TypeToken typeToken) { } // finally try unsafe - return newUnsafeAllocator(type, rawType); + return newUnsafeAllocator(rawType); } private ObjectConstructor newDefaultConstructor(Class rawType) { @@ -125,10 +127,11 @@ public T construct() { } return new ObjectConstructor() { - @SuppressWarnings("unchecked") // T is the same raw type as is requested @Override public T construct() { try { - return (T) constructor.newInstance(); + @SuppressWarnings("unchecked") // T is the same raw type as is requested + T newInstance = (T) constructor.newInstance(); + return newInstance; } catch (InstantiationException e) { // TODO: JsonParseException ? throw new RuntimeException("Failed to invoke " + constructor + " with no args", e); @@ -233,21 +236,32 @@ private ObjectConstructor newDefaultImplementationConstructor( return null; } - private ObjectConstructor newUnsafeAllocator( - final Type type, final Class rawType) { - return new ObjectConstructor() { - private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create(); - @SuppressWarnings("unchecked") - @Override public T construct() { - try { - Object newInstance = unsafeAllocator.newInstance(rawType); - return (T) newInstance; - } catch (Exception e) { - throw new RuntimeException(("Unable to invoke no-args constructor for " + type + ". " - + "Registering an InstanceCreator with Gson for this type may fix this problem."), e); + private ObjectConstructor newUnsafeAllocator(final Class rawType) { + if (useJdkUnsafe) { + return new ObjectConstructor() { + private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create(); + @Override public T construct() { + try { + @SuppressWarnings("unchecked") + T newInstance = (T) unsafeAllocator.newInstance(rawType); + return newInstance; + } catch (Exception e) { + throw new RuntimeException(("Unable to create instance of " + rawType + ". " + + "Registering an InstanceCreator or a TypeAdapter for this type, or adding a no-args " + + "constructor may fix this problem."), e); + } } - } - }; + }; + } else { + final String exceptionMessage = "Unable to create instance of " + rawType + "; usage of JDK Unsafe " + + "is disabled. Registering an InstanceCreator or a TypeAdapter for this type, adding a no-args " + + "constructor, or enabling usage of JDK Unsafe may fix this problem."; + return new ObjectConstructor() { + @Override public T construct() { + throw new JsonIOException(exceptionMessage); + } + }; + } } @Override public String toString() { diff --git a/gson/src/main/java/com/google/gson/internal/UnsafeAllocator.java b/gson/src/main/java/com/google/gson/internal/UnsafeAllocator.java index 999a2b57ef..7060a22eb6 100644 --- a/gson/src/main/java/com/google/gson/internal/UnsafeAllocator.java +++ b/gson/src/main/java/com/google/gson/internal/UnsafeAllocator.java @@ -101,7 +101,8 @@ public T newInstance(Class c) throws Exception { return new UnsafeAllocator() { @Override public T newInstance(Class c) { - throw new UnsupportedOperationException("Cannot allocate " + c); + throw new UnsupportedOperationException("Cannot allocate " + c + ". Usage of JDK sun.misc.Unsafe is enabled, " + + "but it could not be used. Make sure your runtime is configured correctly."); } }; } diff --git a/gson/src/test/java/com/google/gson/GsonBuilderTest.java b/gson/src/test/java/com/google/gson/GsonBuilderTest.java index 73601c0e3c..d1fd0d4fc2 100644 --- a/gson/src/test/java/com/google/gson/GsonBuilderTest.java +++ b/gson/src/test/java/com/google/gson/GsonBuilderTest.java @@ -84,4 +84,27 @@ public void testTransientFieldExclusion() { static class HasTransients { transient String a = "a"; } + + public void testDisableJdkUnsafe() { + Gson gson = new GsonBuilder() + .disableJdkUnsafe() + .create(); + try { + gson.fromJson("{}", ClassWithoutNoArgsConstructor.class); + fail("Expected exception"); + } catch (JsonIOException expected) { + assertEquals( + "Unable to create instance of class com.google.gson.GsonBuilderTest$ClassWithoutNoArgsConstructor; " + + "usage of JDK Unsafe is disabled. Registering an InstanceCreator or a TypeAdapter for this type, " + + "adding a no-args constructor, or enabling usage of JDK Unsafe may fix this problem.", + expected.getMessage() + ); + } + } + + private static class ClassWithoutNoArgsConstructor { + @SuppressWarnings("unused") + public ClassWithoutNoArgsConstructor(String s) { + } + } } diff --git a/gson/src/test/java/com/google/gson/GsonTest.java b/gson/src/test/java/com/google/gson/GsonTest.java index 82c9740ab8..186ceec9bd 100644 --- a/gson/src/test/java/com/google/gson/GsonTest.java +++ b/gson/src/test/java/com/google/gson/GsonTest.java @@ -53,7 +53,7 @@ public final class GsonTest extends TestCase { public void testOverridesDefaultExcluder() { Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY, new HashMap>(), true, false, true, false, - true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, + true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, new ArrayList(), new ArrayList(), new ArrayList(), CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY); @@ -67,7 +67,7 @@ public void testOverridesDefaultExcluder() { public void testClonedTypeAdapterFactoryListsAreIndependent() { Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY, new HashMap>(), true, false, true, false, - true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, + true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, new ArrayList(), new ArrayList(), new ArrayList(), CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY); From bc8858a3d9a79a7d091474b436767b73e8338ac1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Dec 2021 07:15:38 -0800 Subject: [PATCH 86/90] Bump maven-deploy-plugin from 3.0.0-M1 to 3.0.0-M2 (#2044) Bumps [maven-deploy-plugin](https://github.com/apache/maven-deploy-plugin) from 3.0.0-M1 to 3.0.0-M2. - [Release notes](https://github.com/apache/maven-deploy-plugin/releases) - [Commits](https://github.com/apache/maven-deploy-plugin/compare/maven-deploy-plugin-3.0.0-M1...maven-deploy-plugin-3.0.0-M2) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-deploy-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- codegen/pom.xml | 2 +- extras/pom.xml | 2 +- metrics/pom.xml | 2 +- proto/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/codegen/pom.xml b/codegen/pom.xml index 7b26e8afb4..4d232e3a92 100644 --- a/codegen/pom.xml +++ b/codegen/pom.xml @@ -61,7 +61,7 @@ org.apache.maven.plugins maven-deploy-plugin - 3.0.0-M1 + 3.0.0-M2 true diff --git a/extras/pom.xml b/extras/pom.xml index 7215136fe2..0f83b14230 100644 --- a/extras/pom.xml +++ b/extras/pom.xml @@ -47,7 +47,7 @@ org.apache.maven.plugins maven-deploy-plugin - 3.0.0-M1 + 3.0.0-M2 true diff --git a/metrics/pom.xml b/metrics/pom.xml index 613568fa82..cf0afba330 100644 --- a/metrics/pom.xml +++ b/metrics/pom.xml @@ -49,7 +49,7 @@ org.apache.maven.plugins maven-deploy-plugin - 3.0.0-M1 + 3.0.0-M2 true diff --git a/proto/pom.xml b/proto/pom.xml index e3fba45cde..aa13c952a0 100644 --- a/proto/pom.xml +++ b/proto/pom.xml @@ -89,7 +89,7 @@ org.apache.maven.plugins maven-deploy-plugin - 3.0.0-M1 + 3.0.0-M2 true From dc28951fa7e7112f0e229e0a4665ad27a1891d79 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Fri, 31 Dec 2021 16:20:29 +0100 Subject: [PATCH 87/90] Change target Java version to 7 (#2043) * Change target Java version to 7 * Document Gson requirements * Add package-info.java for `stream` package --- README.md | 19 +++++++++++++++++++ codegen/README.md | 2 +- gson/bnd.bnd | 4 ++-- gson/build.gradle | 4 ++-- gson/pom.xml | 6 +++--- .../com/google/gson/internal/$Gson$Types.java | 5 +++-- .../gson/stream/MalformedJsonException.java | 9 ++------- .../com/google/gson/stream/package-info.java | 4 ++++ .../gson/internal/LinkedTreeMapTest.java | 2 +- pom.xml | 2 +- 10 files changed, 38 insertions(+), 19 deletions(-) create mode 100644 gson/src/main/java/com/google/gson/stream/package-info.java diff --git a/README.md b/README.md index dccace4860..351fed1bf8 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,25 @@ Maven: ![Build Status](https://github.com/google/gson/actions/workflows/build.yml/badge.svg) +### Requirements +#### Java version +- Gson 2.9.0 and newer: Java 7 +- Gson 2.8.9 and older: Java 6 + +Despite supporting older Java versions, Gson also provides a JPMS module descriptor (module name `com.google.gson`) for users of Java 9 or newer. + +#### JPMS dependencies (Java 9+) +These are the optional Java Platform Module System (JPMS) JDK modules which Gson depends on. +This only applies when running Java 9 or newer. + +- `java.sql` (optional since Gson 2.8.9) +When this module is present, Gson provides default adapters for some SQL date and time classes. + +- `jdk.unsupported`, respectively class `sun.misc.Unsafe` (optional) +When this module is present, Gson can use the `Unsafe` class to create instances of classes without no-args constructor. +However, care should be taken when relying on this. `Unsafe` is not available in all environments and its usage has some pitfalls, +see [`GsonBuilder.disableJdkUnsafe()`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#disableJdkUnsafe()). + ### Documentation * [API Javadoc](https://www.javadoc.io/doc/com.google.code.gson/gson): Documentation for the current release * [User guide](https://github.com/google/gson/blob/master/UserGuide.md): This guide contains examples on how to use Gson in your code. diff --git a/codegen/README.md b/codegen/README.md index adee425a23..c9a9caf8d9 100644 --- a/codegen/README.md +++ b/codegen/README.md @@ -1,4 +1,4 @@ -# gson-codegen +# codegen This Maven module contains the source code for automatically generating Gson type adapters. diff --git a/gson/bnd.bnd b/gson/bnd.bnd index 07746f0a48..626a0c5bec 100644 --- a/gson/bnd.bnd +++ b/gson/bnd.bnd @@ -3,8 +3,8 @@ Bundle-Name: ${project.name} Bundle-Description: ${project.description} Bundle-Vendor: Google Gson Project Bundle-ContactAddress: ${project.parent.url} -Bundle-RequiredExecutionEnvironment: JavaSE-1.6, JavaSE-1.7, JavaSE-1.8 -Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.6))" +Bundle-RequiredExecutionEnvironment: JavaSE-1.7, JavaSE-1.8 +Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.7))" # Optional dependency for JDK's sun.misc.Unsafe # https://bnd.bndtools.org/chapters/920-faq.html#remove-unwanted-imports- diff --git a/gson/build.gradle b/gson/build.gradle index 4dd24c1d0f..5a8919bcef 100644 --- a/gson/build.gradle +++ b/gson/build.gradle @@ -4,8 +4,8 @@ apply plugin: 'maven' group = 'com.google.code.gson' version = '2.8.6-SNAPSHOT' -sourceCompatibility = 1.6 -targetCompatibility = 1.6 +sourceCompatibility = 1.7 +targetCompatibility = 1.7 sourceSets.main.java.exclude("**/module-info.java") dependencies { diff --git a/gson/pom.xml b/gson/pom.xml index 31ea881f6b..1b1eb0d117 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -58,8 +58,8 @@ [1.5,9) - 1.6 - 1.6 + 1.7 + 1.7 @@ -83,7 +83,7 @@ com.google.gson.internal:com.google.gson.internal.bind - https://docs.oracle.com/javase/6/docs/api/ + https://docs.oracle.com/javase/7/docs/api/ diff --git a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java index 617a644b1f..32a8456504 100644 --- a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java +++ b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java @@ -574,8 +574,9 @@ public Type getGenericComponentType() { /** * The WildcardType interface supports multiple upper bounds and multiple - * lower bounds. We only support what the Java 6 language needs - at most one - * bound. If a lower bound is set, the upper bound must be Object.class. + * lower bounds. We only support what the target Java version supports - at most one + * bound, see also https://bugs.openjdk.java.net/browse/JDK-8250660. If a lower bound + * is set, the upper bound must be Object.class. */ private static final class WildcardTypeImpl implements WildcardType, Serializable { private final Type upperBound; diff --git a/gson/src/main/java/com/google/gson/stream/MalformedJsonException.java b/gson/src/main/java/com/google/gson/stream/MalformedJsonException.java index 9da70ebccd..65b0a7719b 100644 --- a/gson/src/main/java/com/google/gson/stream/MalformedJsonException.java +++ b/gson/src/main/java/com/google/gson/stream/MalformedJsonException.java @@ -30,15 +30,10 @@ public MalformedJsonException(String msg) { } public MalformedJsonException(String msg, Throwable throwable) { - super(msg); - // Using initCause() instead of calling super() because Java 1.5 didn't retrofit IOException - // with a constructor with Throwable. This was done in Java 1.6 - initCause(throwable); + super(msg, throwable); } public MalformedJsonException(Throwable throwable) { - // Using initCause() instead of calling super() because Java 1.5 didn't retrofit IOException - // with a constructor with Throwable. This was done in Java 1.6 - initCause(throwable); + super(throwable); } } diff --git a/gson/src/main/java/com/google/gson/stream/package-info.java b/gson/src/main/java/com/google/gson/stream/package-info.java new file mode 100644 index 0000000000..bed6c62927 --- /dev/null +++ b/gson/src/main/java/com/google/gson/stream/package-info.java @@ -0,0 +1,4 @@ +/** + * This package provides classes for processing JSON in an efficient streaming way. + */ +package com.google.gson.stream; diff --git a/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java b/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java index 68220cf631..fa8b08c3e0 100644 --- a/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java +++ b/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java @@ -161,7 +161,7 @@ public void testJavaSerialization() throws IOException, ClassNotFoundException { } @SafeVarargs - private void assertIterationOrder(Iterable actual, T... expected) { + private final void assertIterationOrder(Iterable actual, T... expected) { ArrayList actualList = new ArrayList(); for (T t : actual) { actualList.add(t); diff --git a/pom.xml b/pom.xml index 5e16a883b1..2c6dc9d617 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ UTF-8 - 1.6 + 1.7 From 6b96a389cc40d56fd3d00547c00a42ab2bef6098 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sat, 1 Jan 2022 21:44:39 +0100 Subject: [PATCH 88/90] Put `module-info.class` into Multi-Release JAR folder (#2013) * Put module-info.class into Multi-Release JAR folder Uses ModiTect to place module-info.class under Multi-Release JAR folder `META-INF/versions/9`. * Adjust pom.xml to drop support for Java 6 * Change doclint setting All Javadoc errors have been solved previously; doclint can now be enabled without causing build failures. * Improve README Java requirements --- README.md | 2 +- gson/pom.xml | 50 ++++++++++-------- .../gson/functional/TreeTypeAdaptersTest.java | 7 +-- pom.xml | 52 +++++++++---------- .../gson/protobuf/ProtoTypeAdapter.java | 8 +-- 5 files changed, 60 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 351fed1bf8..a9ec0423f8 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Maven: ![Build Status](https://github.com/google/gson/actions/workflows/build.yml/badge.svg) ### Requirements -#### Java version +#### Minimum Java version - Gson 2.9.0 and newer: Java 7 - Gson 2.8.9 and older: Java 6 diff --git a/gson/pom.xml b/gson/pom.xml index 1b1eb0d117..c1be6a920b 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -35,33 +35,18 @@ default-compile - - - 9 - - 9 - - - - base-compile - - compile - + module-info.java - - - [1.5,9) - - 1.7 - 1.7 - + org.apache.maven.plugins maven-surefire-plugin @@ -82,11 +67,31 @@ maven-javadoc-plugin com.google.gson.internal:com.google.gson.internal.bind - - https://docs.oracle.com/javase/7/docs/api/ - + + + + org.moditect + moditect-maven-plugin + 1.0.0.RC2 + + + add-module-info + package + + add-module-info + + + 9 + + ${project.build.sourceDirectory}/module-info.java + + + + + biz.aQute.bnd bnd-maven-plugin @@ -104,6 +109,7 @@ maven-jar-plugin + ${project.build.outputDirectory}/META-INF/MANIFEST.MF diff --git a/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java index dd412ea131..afcc4bccda 100644 --- a/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java @@ -57,7 +57,7 @@ protected void setUp() { .registerTypeAdapter(Id.class, new IdTreeTypeAdapter()) .create(); course = new Course(COURSE_ID, 4, - new Assignment(null, null), createList(STUDENT1, STUDENT2)); + new Assignment(null, null), Arrays.asList(STUDENT1, STUDENT2)); } public void testSerializeId() { @@ -171,9 +171,4 @@ public Assignment(Id> id, T data) { private static class HistoryCourse { int numClasses; } - - @SafeVarargs - private static List createList(T ...items) { - return Arrays.asList(items); - } } diff --git a/pom.xml b/pom.xml index 2c6dc9d617..0e888b64f2 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ UTF-8 - 1.7 + 7 @@ -68,23 +68,42 @@ org.apache.maven.plugins maven-compiler-plugin 3.8.1 + + ${javaVersion} + + [11,) + + org.apache.maven.plugins maven-javadoc-plugin 3.3.1 + + + [11,) + + + all,-missing + + false + + https://docs.oracle.com/en/java/javase/11/docs/api/ + + + false + org.apache.maven.plugins maven-jar-plugin 3.2.0 - - org.apache.felix - maven-bundle-plugin - 5.1.3 - true - @@ -110,23 +129,4 @@ - - - doclint-java8-disable - - [1.8,) - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - -Xdoclint:none - - - - - - diff --git a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java index d588d82404..742916b998 100644 --- a/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java +++ b/proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java @@ -191,7 +191,7 @@ public static Builder newBuilder() { private static final com.google.protobuf.Descriptors.FieldDescriptor.Type ENUM_TYPE = com.google.protobuf.Descriptors.FieldDescriptor.Type.ENUM; - private static final ConcurrentMap, Method>> mapOfMapOfMethods = + private static final ConcurrentMap, Method>> mapOfMapOfMethods = new MapMaker().makeMap(); private final EnumSerialization enumSerialization; @@ -308,7 +308,7 @@ public Message deserialize(JsonElement json, Type typeOfT, } } } - return (Message) protoBuilder.build(); + return protoBuilder.build(); } catch (SecurityException e) { throw new JsonParseException(e); } catch (NoSuchMethodException e) { @@ -396,10 +396,10 @@ private EnumValueDescriptor findValueByNameAndExtension(EnumDescriptor desc, private static Method getCachedMethod(Class clazz, String methodName, Class... methodParamTypes) throws NoSuchMethodException { - Map, Method> mapOfMethods = mapOfMapOfMethods.get(methodName); + ConcurrentMap, Method> mapOfMethods = mapOfMapOfMethods.get(methodName); if (mapOfMethods == null) { mapOfMethods = new MapMaker().makeMap(); - Map, Method> previous = + ConcurrentMap, Method> previous = mapOfMapOfMethods.putIfAbsent(methodName, mapOfMethods); mapOfMethods = previous == null ? mapOfMethods : previous; } From f26525221d48e344f3237e21ecae0afda7d1d582 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 08:47:50 -0800 Subject: [PATCH 89/90] Bump maven-scm-api from 1.11.3 to 1.12.2 (#2046) Bumps [maven-scm-api](https://github.com/apache/maven-scm) from 1.11.3 to 1.12.2. - [Release notes](https://github.com/apache/maven-scm/releases) - [Commits](https://github.com/apache/maven-scm/compare/maven-scm-1.11.3...maven-scm-1.12.2) --- updated-dependencies: - dependency-name: org.apache.maven.scm:maven-scm-api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0e888b64f2..495c89e739 100644 --- a/pom.xml +++ b/pom.xml @@ -115,7 +115,7 @@ org.apache.maven.scm maven-scm-api - 1.11.3 + 1.12.2 org.apache.maven.scm From 4ec67c00a08348bff6abe1a4a285f8a78483ee37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 08:48:14 -0800 Subject: [PATCH 90/90] Bump maven-scm-provider-gitexe from 1.12.0 to 1.12.2 (#2047) Bumps maven-scm-provider-gitexe from 1.12.0 to 1.12.2. --- updated-dependencies: - dependency-name: org.apache.maven.scm:maven-scm-provider-gitexe dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 495c89e739..e8cee9222d 100644 --- a/pom.xml +++ b/pom.xml @@ -120,7 +120,7 @@ org.apache.maven.scm maven-scm-provider-gitexe - 1.12.0 + 1.12.2

If {@link #SUPPORTS_SQL_TYPES} is {@code true}, all other + * constants of this class will be non-{@code null}. However, if + * it is {@code false} all other constants will be {@code null} and + * there will be no support for {@code java.sql} types. + */ +public final class SqlTypesSupport { + /** + * {@code true} if {@code java.sql} types are supported, + * {@code false} otherwise + */ + public static final boolean SUPPORTS_SQL_TYPES; + + public static final DateType DATE_DATE_TYPE; + public static final DateType TIMESTAMP_DATE_TYPE; + + public static final TypeAdapterFactory DATE_FACTORY; + public static final TypeAdapterFactory TIME_FACTORY; + public static final TypeAdapterFactory TIMESTAMP_FACTORY; + + static { + boolean sqlTypesSupport; + try { + Class.forName("java.sql.Date"); + sqlTypesSupport = true; + } catch (ClassNotFoundException classNotFoundException) { + sqlTypesSupport = false; + } + SUPPORTS_SQL_TYPES = sqlTypesSupport; + + if (SUPPORTS_SQL_TYPES) { + DATE_DATE_TYPE = new DateType(java.sql.Date.class) { + @Override protected java.sql.Date deserialize(Date date) { + return new java.sql.Date(date.getTime()); + } + }; + TIMESTAMP_DATE_TYPE = new DateType(Timestamp.class) { + @Override protected Timestamp deserialize(Date date) { + return new Timestamp(date.getTime()); + } + }; + + DATE_FACTORY = SqlDateTypeAdapter.FACTORY; + TIME_FACTORY = SqlTimeTypeAdapter.FACTORY; + TIMESTAMP_FACTORY = SqlTimestampTypeAdapter.FACTORY; + } else { + DATE_DATE_TYPE = null; + TIMESTAMP_DATE_TYPE = null; + + DATE_FACTORY = null; + TIME_FACTORY = null; + TIMESTAMP_FACTORY = null; + } + } + + private SqlTypesSupport() { + } +} diff --git a/gson/src/main/java/module-info.java b/gson/src/main/java/module-info.java index 161fbdba7f..38c26e569c 100644 --- a/gson/src/main/java/module-info.java +++ b/gson/src/main/java/module-info.java @@ -8,5 +8,6 @@ exports com.google.gson.reflect; exports com.google.gson.stream; - requires transitive java.sql; + // Optional dependency on java.sql + requires static java.sql; } diff --git a/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java b/gson/src/test/java/com/google/gson/internal/bind/DefaultDateTypeAdapterTest.java similarity index 64% rename from gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java rename to gson/src/test/java/com/google/gson/internal/bind/DefaultDateTypeAdapterTest.java index e626ea7aff..2fdc64aeda 100644 --- a/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java +++ b/gson/src/test/java/com/google/gson/internal/bind/DefaultDateTypeAdapterTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.gson; +package com.google.gson.internal.bind; import java.io.IOException; import java.text.DateFormat; @@ -23,8 +23,13 @@ import java.util.Locale; import java.util.TimeZone; -import com.google.gson.DefaultDateTypeAdapter.DateType; +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; import com.google.gson.internal.JavaVersion; +import com.google.gson.internal.bind.DefaultDateTypeAdapter; +import com.google.gson.internal.bind.DefaultDateTypeAdapter.DateType; +import com.google.gson.reflect.TypeToken; import junit.framework.TestCase; @@ -53,18 +58,18 @@ private void assertFormattingAlwaysEmitsUsLocale(Locale locale) { String afterYearLongSep = JavaVersion.isJava9OrLater() ? " at " : " "; String utcFull = JavaVersion.isJava9OrLater() ? "Coordinated Universal Time" : "UTC"; assertFormatted(String.format("Jan 1, 1970%s12:00:00 AM", afterYearSep), - DateType.DATE.createDefaultsAdapter()); - assertFormatted("1/1/70", DateType.DATE.createAdapter(DateFormat.SHORT)); - assertFormatted("Jan 1, 1970", DateType.DATE.createAdapter(DateFormat.MEDIUM)); - assertFormatted("January 1, 1970", DateType.DATE.createAdapter(DateFormat.LONG)); + DateType.DATE.createDefaultsAdapterFactory()); + assertFormatted("1/1/70", DateType.DATE.createAdapterFactory(DateFormat.SHORT)); + assertFormatted("Jan 1, 1970", DateType.DATE.createAdapterFactory(DateFormat.MEDIUM)); + assertFormatted("January 1, 1970", DateType.DATE.createAdapterFactory(DateFormat.LONG)); assertFormatted(String.format("1/1/70%s12:00 AM", afterYearSep), - DateType.DATE.createAdapter(DateFormat.SHORT, DateFormat.SHORT)); + DateType.DATE.createAdapterFactory(DateFormat.SHORT, DateFormat.SHORT)); assertFormatted(String.format("Jan 1, 1970%s12:00:00 AM", afterYearSep), - DateType.DATE.createAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM)); + DateType.DATE.createAdapterFactory(DateFormat.MEDIUM, DateFormat.MEDIUM)); assertFormatted(String.format("January 1, 1970%s12:00:00 AM UTC", afterYearLongSep), - DateType.DATE.createAdapter(DateFormat.LONG, DateFormat.LONG)); + DateType.DATE.createAdapterFactory(DateFormat.LONG, DateFormat.LONG)); assertFormatted(String.format("Thursday, January 1, 1970%s12:00:00 AM %s", afterYearLongSep, utcFull), - DateType.DATE.createAdapter(DateFormat.FULL, DateFormat.FULL)); + DateType.DATE.createAdapterFactory(DateFormat.FULL, DateFormat.FULL)); } finally { TimeZone.setDefault(defaultTimeZone); Locale.setDefault(defaultLocale); @@ -79,21 +84,21 @@ public void testParsingDatesFormattedWithSystemLocale() throws Exception { try { String afterYearSep = JavaVersion.isJava9OrLater() ? " à " : " "; assertParsed(String.format("1 janv. 1970%s00:00:00", afterYearSep), - DateType.DATE.createDefaultsAdapter()); - assertParsed("01/01/70", DateType.DATE.createAdapter(DateFormat.SHORT)); - assertParsed("1 janv. 1970", DateType.DATE.createAdapter(DateFormat.MEDIUM)); - assertParsed("1 janvier 1970", DateType.DATE.createAdapter(DateFormat.LONG)); + DateType.DATE.createDefaultsAdapterFactory()); + assertParsed("01/01/70", DateType.DATE.createAdapterFactory(DateFormat.SHORT)); + assertParsed("1 janv. 1970", DateType.DATE.createAdapterFactory(DateFormat.MEDIUM)); + assertParsed("1 janvier 1970", DateType.DATE.createAdapterFactory(DateFormat.LONG)); assertParsed("01/01/70 00:00", - DateType.DATE.createAdapter(DateFormat.SHORT, DateFormat.SHORT)); + DateType.DATE.createAdapterFactory(DateFormat.SHORT, DateFormat.SHORT)); assertParsed(String.format("1 janv. 1970%s00:00:00", afterYearSep), - DateType.DATE.createAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM)); + DateType.DATE.createAdapterFactory(DateFormat.MEDIUM, DateFormat.MEDIUM)); assertParsed(String.format("1 janvier 1970%s00:00:00 UTC", afterYearSep), - DateType.DATE.createAdapter(DateFormat.LONG, DateFormat.LONG)); + DateType.DATE.createAdapterFactory(DateFormat.LONG, DateFormat.LONG)); assertParsed(JavaVersion.isJava9OrLater() ? (JavaVersion.getMajorJavaVersion() <11 ? "jeudi 1 janvier 1970 à 00:00:00 Coordinated Universal Time" : "jeudi 1 janvier 1970 à 00:00:00 Temps universel coordonné") : "jeudi 1 janvier 1970 00 h 00 UTC", - DateType.DATE.createAdapter(DateFormat.FULL, DateFormat.FULL)); + DateType.DATE.createAdapterFactory(DateFormat.FULL, DateFormat.FULL)); } finally { TimeZone.setDefault(defaultTimeZone); Locale.setDefault(defaultLocale); @@ -106,18 +111,18 @@ public void testParsingDatesFormattedWithUsLocale() throws Exception { Locale defaultLocale = Locale.getDefault(); Locale.setDefault(Locale.US); try { - assertParsed("Jan 1, 1970 0:00:00 AM", DateType.DATE.createDefaultsAdapter()); - assertParsed("1/1/70", DateType.DATE.createAdapter(DateFormat.SHORT)); - assertParsed("Jan 1, 1970", DateType.DATE.createAdapter(DateFormat.MEDIUM)); - assertParsed("January 1, 1970", DateType.DATE.createAdapter(DateFormat.LONG)); + assertParsed("Jan 1, 1970 0:00:00 AM", DateType.DATE.createDefaultsAdapterFactory()); + assertParsed("1/1/70", DateType.DATE.createAdapterFactory(DateFormat.SHORT)); + assertParsed("Jan 1, 1970", DateType.DATE.createAdapterFactory(DateFormat.MEDIUM)); + assertParsed("January 1, 1970", DateType.DATE.createAdapterFactory(DateFormat.LONG)); assertParsed("1/1/70 0:00 AM", - DateType.DATE.createAdapter(DateFormat.SHORT, DateFormat.SHORT)); + DateType.DATE.createAdapterFactory(DateFormat.SHORT, DateFormat.SHORT)); assertParsed("Jan 1, 1970 0:00:00 AM", - DateType.DATE.createAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM)); + DateType.DATE.createAdapterFactory(DateFormat.MEDIUM, DateFormat.MEDIUM)); assertParsed("January 1, 1970 0:00:00 AM UTC", - DateType.DATE.createAdapter(DateFormat.LONG, DateFormat.LONG)); + DateType.DATE.createAdapterFactory(DateFormat.LONG, DateFormat.LONG)); assertParsed("Thursday, January 1, 1970 0:00:00 AM UTC", - DateType.DATE.createAdapter(DateFormat.FULL, DateFormat.FULL)); + DateType.DATE.createAdapterFactory(DateFormat.FULL, DateFormat.FULL)); } finally { TimeZone.setDefault(defaultTimeZone); Locale.setDefault(defaultLocale); @@ -132,8 +137,8 @@ public void testFormatUsesDefaultTimezone() throws Exception { try { String afterYearSep = JavaVersion.isJava9OrLater() ? ", " : " "; assertFormatted(String.format("Dec 31, 1969%s4:00:00 PM", afterYearSep), - DateType.DATE.createDefaultsAdapter()); - assertParsed("Dec 31, 1969 4:00:00 PM", DateType.DATE.createDefaultsAdapter()); + DateType.DATE.createDefaultsAdapterFactory()); + assertParsed("Dec 31, 1969 4:00:00 PM", DateType.DATE.createDefaultsAdapterFactory()); } finally { TimeZone.setDefault(defaultTimeZone); Locale.setDefault(defaultLocale); @@ -141,17 +146,17 @@ public void testFormatUsesDefaultTimezone() throws Exception { } public void testDateDeserializationISO8601() throws Exception { - DefaultDateTypeAdapter adapter = DateType.DATE.createDefaultsAdapter(); - assertParsed("1970-01-01T00:00:00.000Z", adapter); - assertParsed("1970-01-01T00:00Z", adapter); - assertParsed("1970-01-01T00:00:00+00:00", adapter); - assertParsed("1970-01-01T01:00:00+01:00", adapter); - assertParsed("1970-01-01T01:00:00+01", adapter); - } - + TypeAdapterFactory adapterFactory = DateType.DATE.createDefaultsAdapterFactory(); + assertParsed("1970-01-01T00:00:00.000Z", adapterFactory); + assertParsed("1970-01-01T00:00Z", adapterFactory); + assertParsed("1970-01-01T00:00:00+00:00", adapterFactory); + assertParsed("1970-01-01T01:00:00+01:00", adapterFactory); + assertParsed("1970-01-01T01:00:00+01", adapterFactory); + } + public void testDateSerialization() throws Exception { int dateStyle = DateFormat.LONG; - DefaultDateTypeAdapter dateTypeAdapter = DateType.DATE.createAdapter(dateStyle); + TypeAdapter dateTypeAdapter = dateAdapter(DateType.DATE.createAdapterFactory(dateStyle)); DateFormat formatter = DateFormat.getDateInstance(dateStyle, Locale.US); Date currentDate = new Date(); @@ -161,7 +166,7 @@ public void testDateSerialization() throws Exception { public void testDatePattern() throws Exception { String pattern = "yyyy-MM-dd"; - DefaultDateTypeAdapter dateTypeAdapter = DateType.DATE.createAdapter(pattern); + TypeAdapter dateTypeAdapter = dateAdapter(DateType.DATE.createAdapterFactory(pattern)); DateFormat formatter = new SimpleDateFormat(pattern); Date currentDate = new Date(); @@ -171,30 +176,38 @@ public void testDatePattern() throws Exception { public void testInvalidDatePattern() throws Exception { try { - DateType.DATE.createAdapter("I am a bad Date pattern...."); + DateType.DATE.createAdapterFactory("I am a bad Date pattern...."); fail("Invalid date pattern should fail."); } catch (IllegalArgumentException expected) { } } public void testNullValue() throws Exception { - DefaultDateTypeAdapter adapter = DateType.DATE.createDefaultsAdapter(); + TypeAdapter adapter = dateAdapter(DateType.DATE.createDefaultsAdapterFactory()); assertNull(adapter.fromJson("null")); assertEquals("null", adapter.toJson(null)); } public void testUnexpectedToken() throws Exception { try { - DefaultDateTypeAdapter adapter = DateType.DATE.createDefaultsAdapter(); + TypeAdapter adapter = dateAdapter(DateType.DATE.createDefaultsAdapterFactory()); adapter.fromJson("{}"); fail("Unexpected token should fail."); } catch (IllegalStateException expected) { } } - private void assertFormatted(String formatted, DefaultDateTypeAdapter adapter) { + private static TypeAdapter dateAdapter(TypeAdapterFactory adapterFactory) { + TypeAdapter adapter = adapterFactory.create(new Gson(), TypeToken.get(Date.class)); + assertNotNull(adapter); + return adapter; + } + + private static void assertFormatted(String formatted, TypeAdapterFactory adapterFactory) { + TypeAdapter adapter = dateAdapter(adapterFactory); assertEquals(toLiteral(formatted), adapter.toJson(new Date(0))); } - private void assertParsed(String date, DefaultDateTypeAdapter adapter) throws IOException { + private static void assertParsed(String date, TypeAdapterFactory adapterFactory) throws IOException { + TypeAdapter adapter = dateAdapter(adapterFactory); assertEquals(date, new Date(0), adapter.fromJson(toLiteral(date))); assertEquals("ISO 8601", new Date(0), adapter.fromJson(toLiteral("1970-01-01T00:00:00Z"))); } diff --git a/gson/src/test/java/com/google/gson/internal/sql/SqlTypesSupportTest.java b/gson/src/test/java/com/google/gson/internal/sql/SqlTypesSupportTest.java new file mode 100644 index 0000000000..ea496f4ac9 --- /dev/null +++ b/gson/src/test/java/com/google/gson/internal/sql/SqlTypesSupportTest.java @@ -0,0 +1,16 @@ +package com.google.gson.internal.sql; + +import junit.framework.TestCase; + +public class SqlTypesSupportTest extends TestCase { + public void testSupported() { + assertTrue(SqlTypesSupport.SUPPORTS_SQL_TYPES); + + assertNotNull(SqlTypesSupport.DATE_DATE_TYPE); + assertNotNull(SqlTypesSupport.TIMESTAMP_DATE_TYPE); + + assertNotNull(SqlTypesSupport.DATE_FACTORY); + assertNotNull(SqlTypesSupport.TIME_FACTORY); + assertNotNull(SqlTypesSupport.TIMESTAMP_FACTORY); + } +} From 4fb215c9df408ef48bad1288ef23c68b7910a7a3 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sat, 23 May 2020 22:50:14 +0200 Subject: [PATCH 06/90] Move SQL types specific tests to separate test class --- .../functional/DefaultTypeAdaptersTest.java | 95 ++------------ .../gson/internal/sql/SqlTypesGsonTest.java | 124 ++++++++++++++++++ 2 files changed, 135 insertions(+), 84 deletions(-) create mode 100644 gson/src/test/java/com/google/gson/internal/sql/SqlTypesGsonTest.java diff --git a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java index b7307c6fce..0dae4ede22 100644 --- a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java @@ -69,12 +69,14 @@ public class DefaultTypeAdaptersTest extends TestCase { private Gson gson; private TimeZone oldTimeZone; + private Locale oldLocale; @Override protected void setUp() throws Exception { super.setUp(); this.oldTimeZone = TimeZone.getDefault(); TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); + this.oldLocale = Locale.getDefault(); Locale.setDefault(Locale.US); gson = new Gson(); } @@ -82,6 +84,7 @@ protected void setUp() throws Exception { @Override protected void tearDown() throws Exception { TimeZone.setDefault(oldTimeZone); + Locale.setDefault(oldLocale); super.tearDown(); } @@ -146,7 +149,7 @@ public void testUriDeserialization() { URI target = gson.fromJson(json, URI.class); assertEquals(uriValue, target.toASCIIString()); } - + public void testNullSerialization() throws Exception { testNullSerializationAndDeserialization(Boolean.class); testNullSerializationAndDeserialization(Byte.class); @@ -175,14 +178,15 @@ public void testNullSerialization() throws Exception { testNullSerializationAndDeserialization(Date.class); testNullSerializationAndDeserialization(GregorianCalendar.class); testNullSerializationAndDeserialization(Calendar.class); - testNullSerializationAndDeserialization(Time.class); - testNullSerializationAndDeserialization(Timestamp.class); - testNullSerializationAndDeserialization(java.sql.Date.class); testNullSerializationAndDeserialization(Enum.class); testNullSerializationAndDeserialization(Class.class); } private void testNullSerializationAndDeserialization(Class c) { + testNullSerializationAndDeserialization(gson, c); + } + + public static void testNullSerializationAndDeserialization(Gson gson, Class c) { assertEquals("null", gson.toJson(null, c)); assertEquals(null, gson.fromJson("null", c)); } @@ -269,7 +273,7 @@ public void testBigIntegerFieldDeserialization() { ClassWithBigInteger actual = gson.fromJson(json, ClassWithBigInteger.class); assertEquals(expected.value, actual.value); } - + public void testOverrideBigIntegerTypeAdapter() throws Exception { gson = new GsonBuilder() .registerTypeAdapter(BigInteger.class, new NumberAsStringAdapter(BigInteger.class)) @@ -347,60 +351,19 @@ public void testDefaultDateDeserialization() { // Date can not directly be compared with another instance since the deserialization loses the // millisecond portion. @SuppressWarnings("deprecation") - private void assertEqualsDate(Date date, int year, int month, int day) { + public static void assertEqualsDate(Date date, int year, int month, int day) { assertEquals(year-1900, date.getYear()); assertEquals(month, date.getMonth()); assertEquals(day, date.getDate()); } @SuppressWarnings("deprecation") - private void assertEqualsTime(Date date, int hours, int minutes, int seconds) { + public static void assertEqualsTime(Date date, int hours, int minutes, int seconds) { assertEquals(hours, date.getHours()); assertEquals(minutes, date.getMinutes()); assertEquals(seconds, date.getSeconds()); } - public void testDefaultJavaSqlDateSerialization() { - java.sql.Date instant = new java.sql.Date(1259875082000L); - String json = gson.toJson(instant); - assertEquals("\"Dec 3, 2009\"", json); - } - - public void testDefaultJavaSqlDateDeserialization() { - String json = "'Dec 3, 2009'"; - java.sql.Date extracted = gson.fromJson(json, java.sql.Date.class); - assertEqualsDate(extracted, 2009, 11, 3); - } - - public void testDefaultJavaSqlTimestampSerialization() { - Timestamp now = new java.sql.Timestamp(1259875082000L); - String json = gson.toJson(now); - if (JavaVersion.isJava9OrLater()) { - assertEquals("\"Dec 3, 2009, 1:18:02 PM\"", json); - } else { - assertEquals("\"Dec 3, 2009 1:18:02 PM\"", json); - } - } - - public void testDefaultJavaSqlTimestampDeserialization() { - String json = "'Dec 3, 2009 1:18:02 PM'"; - Timestamp extracted = gson.fromJson(json, Timestamp.class); - assertEqualsDate(extracted, 2009, 11, 3); - assertEqualsTime(extracted, 13, 18, 2); - } - - public void testDefaultJavaSqlTimeSerialization() { - Time now = new Time(1259875082000L); - String json = gson.toJson(now); - assertEquals("\"01:18:02 PM\"", json); - } - - public void testDefaultJavaSqlTimeDeserialization() { - String json = "'1:18:02 PM'"; - Time extracted = gson.fromJson(json, Time.class); - assertEqualsTime(extracted, 13, 18, 2); - } - public void testDefaultDateSerializationUsingBuilder() throws Exception { Gson gson = new GsonBuilder().create(); Date now = new Date(1315806903103L); @@ -524,42 +487,6 @@ public void testDateSerializationInCollection() throws Exception { } } - // http://code.google.com/p/google-gson/issues/detail?id=230 - public void testTimestampSerialization() throws Exception { - TimeZone defaultTimeZone = TimeZone.getDefault(); - TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - Locale defaultLocale = Locale.getDefault(); - Locale.setDefault(Locale.US); - try { - Timestamp timestamp = new Timestamp(0L); - Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd").create(); - String json = gson.toJson(timestamp, Timestamp.class); - assertEquals("\"1970-01-01\"", json); - assertEquals(0, gson.fromJson("\"1970-01-01\"", Timestamp.class).getTime()); - } finally { - TimeZone.setDefault(defaultTimeZone); - Locale.setDefault(defaultLocale); - } - } - - // http://code.google.com/p/google-gson/issues/detail?id=230 - public void testSqlDateSerialization() throws Exception { - TimeZone defaultTimeZone = TimeZone.getDefault(); - TimeZone.setDefault(TimeZone.getTimeZone("UTC")); - Locale defaultLocale = Locale.getDefault(); - Locale.setDefault(Locale.US); - try { - java.sql.Date sqlDate = new java.sql.Date(0L); - Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd").create(); - String json = gson.toJson(sqlDate, Timestamp.class); - assertEquals("\"1970-01-01\"", json); - assertEquals(0, gson.fromJson("\"1970-01-01\"", java.sql.Date.class).getTime()); - } finally { - TimeZone.setDefault(defaultTimeZone); - Locale.setDefault(defaultLocale); - } - } - public void testJsonPrimitiveSerialization() { assertEquals("5", gson.toJson(new JsonPrimitive(5), JsonElement.class)); assertEquals("true", gson.toJson(new JsonPrimitive(true), JsonElement.class)); diff --git a/gson/src/test/java/com/google/gson/internal/sql/SqlTypesGsonTest.java b/gson/src/test/java/com/google/gson/internal/sql/SqlTypesGsonTest.java new file mode 100644 index 0000000000..03e2185541 --- /dev/null +++ b/gson/src/test/java/com/google/gson/internal/sql/SqlTypesGsonTest.java @@ -0,0 +1,124 @@ +package com.google.gson.internal.sql; + +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Locale; +import java.util.TimeZone; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.functional.DefaultTypeAdaptersTest; +import com.google.gson.internal.JavaVersion; + +import junit.framework.TestCase; + +public class SqlTypesGsonTest extends TestCase { + private Gson gson; + private TimeZone oldTimeZone; + private Locale oldLocale; + + @Override + protected void setUp() throws Exception { + super.setUp(); + this.oldTimeZone = TimeZone.getDefault(); + TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); + this.oldLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + gson = new Gson(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + TimeZone.setDefault(oldTimeZone); + Locale.setDefault(oldLocale); + } + + public void testNullSerializationAndDeserialization() { + testNullSerializationAndDeserialization(Date.class); + testNullSerializationAndDeserialization(Time.class); + testNullSerializationAndDeserialization(Timestamp.class); + } + + private void testNullSerializationAndDeserialization(Class c) { + DefaultTypeAdaptersTest.testNullSerializationAndDeserialization(gson, c); + } + + public void testDefaultSqlDateSerialization() { + java.sql.Date instant = new java.sql.Date(1259875082000L); + String json = gson.toJson(instant); + assertEquals("\"Dec 3, 2009\"", json); + } + + public void testDefaultSqlDateDeserialization() { + String json = "'Dec 3, 2009'"; + java.sql.Date extracted = gson.fromJson(json, java.sql.Date.class); + DefaultTypeAdaptersTest.assertEqualsDate(extracted, 2009, 11, 3); + } + + // http://code.google.com/p/google-gson/issues/detail?id=230 + public void testSqlDateSerialization() throws Exception { + TimeZone defaultTimeZone = TimeZone.getDefault(); + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + try { + java.sql.Date sqlDate = new java.sql.Date(0L); + Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd").create(); + String json = gson.toJson(sqlDate, Timestamp.class); + assertEquals("\"1970-01-01\"", json); + assertEquals(0, gson.fromJson("\"1970-01-01\"", java.sql.Date.class).getTime()); + } finally { + TimeZone.setDefault(defaultTimeZone); + Locale.setDefault(defaultLocale); + } + } + + public void testDefaultSqlTimeSerialization() { + Time now = new Time(1259875082000L); + String json = gson.toJson(now); + assertEquals("\"01:18:02 PM\"", json); + } + + public void testDefaultSqlTimeDeserialization() { + String json = "'1:18:02 PM'"; + Time extracted = gson.fromJson(json, Time.class); + DefaultTypeAdaptersTest.assertEqualsTime(extracted, 13, 18, 2); + } + + public void testDefaultSqlTimestampSerialization() { + Timestamp now = new java.sql.Timestamp(1259875082000L); + String json = gson.toJson(now); + if (JavaVersion.isJava9OrLater()) { + assertEquals("\"Dec 3, 2009, 1:18:02 PM\"", json); + } else { + assertEquals("\"Dec 3, 2009 1:18:02 PM\"", json); + } + } + + public void testDefaultSqlTimestampDeserialization() { + String json = "'Dec 3, 2009 1:18:02 PM'"; + Timestamp extracted = gson.fromJson(json, Timestamp.class); + DefaultTypeAdaptersTest.assertEqualsDate(extracted, 2009, 11, 3); + DefaultTypeAdaptersTest.assertEqualsTime(extracted, 13, 18, 2); + } + + // http://code.google.com/p/google-gson/issues/detail?id=230 + public void testTimestampSerialization() throws Exception { + TimeZone defaultTimeZone = TimeZone.getDefault(); + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + try { + Timestamp timestamp = new Timestamp(0L); + Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd").create(); + String json = gson.toJson(timestamp, Timestamp.class); + assertEquals("\"1970-01-01\"", json); + assertEquals(0, gson.fromJson("\"1970-01-01\"", Timestamp.class).getTime()); + } finally { + TimeZone.setDefault(defaultTimeZone); + Locale.setDefault(defaultLocale); + } + } +} From a4a235e14a63b9a4f902805641f1160c6bf5aa93 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 24 May 2020 23:54:32 +0200 Subject: [PATCH 07/90] Remove redundant validation method --- .../gson/internal/bind/DefaultDateTypeAdapter.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/DefaultDateTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/DefaultDateTypeAdapter.java index 9ac948f784..f56faee0f9 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/DefaultDateTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/DefaultDateTypeAdapter.java @@ -29,6 +29,7 @@ import com.google.gson.JsonSyntaxException; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.$Gson$Preconditions; import com.google.gson.internal.JavaVersion; import com.google.gson.internal.PreJava9DateFormatProvider; import com.google.gson.internal.bind.util.ISO8601Utils; @@ -92,7 +93,7 @@ public final TypeAdapterFactory createDefaultsAdapterFactory() { private final List dateFormats = new ArrayList(); private DefaultDateTypeAdapter(DateType dateType, String datePattern) { - this.dateType = verifyDateType(dateType); + this.dateType = $Gson$Preconditions.checkNotNull(dateType); dateFormats.add(new SimpleDateFormat(datePattern, Locale.US)); if (!Locale.getDefault().equals(Locale.US)) { dateFormats.add(new SimpleDateFormat(datePattern)); @@ -100,7 +101,7 @@ private DefaultDateTypeAdapter(DateType dateType, String datePattern) { } private DefaultDateTypeAdapter(DateType dateType, int style) { - this.dateType = verifyDateType(dateType); + this.dateType = $Gson$Preconditions.checkNotNull(dateType); dateFormats.add(DateFormat.getDateInstance(style, Locale.US)); if (!Locale.getDefault().equals(Locale.US)) { dateFormats.add(DateFormat.getDateInstance(style)); @@ -111,7 +112,7 @@ private DefaultDateTypeAdapter(DateType dateType, int style) { } private DefaultDateTypeAdapter(DateType dateType, int dateStyle, int timeStyle) { - this.dateType = verifyDateType(dateType); + this.dateType = $Gson$Preconditions.checkNotNull(dateType); dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US)); if (!Locale.getDefault().equals(Locale.US)) { dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle)); @@ -121,13 +122,6 @@ private DefaultDateTypeAdapter(DateType dateType, int dateStyle, int timeStyl } } - private static DateType verifyDateType(DateType dateType) { - if (dateType == null) { - throw new NullPointerException("dateType == null"); - } - return dateType; - } - // These methods need to be synchronized since JDK DateFormat classes are not thread-safe // See issue 162 @Override From b39494dbe68f91045850778cac4b661b38beb615 Mon Sep 17 00:00:00 2001 From: Richard Hernandez Date: Tue, 26 May 2020 20:12:06 -0700 Subject: [PATCH 08/90] Fix fallback behavior of UnsafeReflectionAllocator when AccessibleObject isn't so accessible --- .../reflect/UnsafeReflectionAccessor.java | 2 +- .../reflect/UnsafeReflectionAccessorTest.java | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java b/gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java index 749335b77a..b23d7babec 100644 --- a/gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java +++ b/gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java @@ -79,7 +79,7 @@ private static Object getUnsafeInstance() { private static Field getOverrideField() { try { return AccessibleObject.class.getDeclaredField("override"); - } catch (NoSuchFieldException e) { + } catch (Exception e) { return null; } } diff --git a/gson/src/test/java/com/google/gson/internal/reflect/UnsafeReflectionAccessorTest.java b/gson/src/test/java/com/google/gson/internal/reflect/UnsafeReflectionAccessorTest.java index d5caaf5375..b330e66209 100644 --- a/gson/src/test/java/com/google/gson/internal/reflect/UnsafeReflectionAccessorTest.java +++ b/gson/src/test/java/com/google/gson/internal/reflect/UnsafeReflectionAccessorTest.java @@ -15,10 +15,12 @@ */ package com.google.gson.internal.reflect; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.lang.reflect.Field; +import java.security.Permission; import org.junit.Test; @@ -41,6 +43,30 @@ public void testMakeAccessibleWithUnsafe() throws Exception { } } + @Test + public void testMakeAccessibleWithRestrictiveSecurityManager() throws Exception { + final Permission accessDeclaredMembers = new RuntimePermission("accessDeclaredMembers"); + final SecurityManager original = System.getSecurityManager(); + SecurityManager restrictiveManager = new SecurityManager() { + @Override + public void checkPermission(Permission perm) { + if (accessDeclaredMembers.equals(perm)) { + throw new SecurityException("nope"); + } + } + }; + System.setSecurityManager(restrictiveManager); + + try { + UnsafeReflectionAccessor accessor = new UnsafeReflectionAccessor(); + Field field = ClassWithPrivateFinalFields.class.getDeclaredField("a"); + assertFalse("override field should have been inaccessible", accessor.makeAccessibleWithUnsafe(field)); + accessor.makeAccessible(field); + } finally { + System.setSecurityManager(original); + } + } + @SuppressWarnings("unused") private static final class ClassWithPrivateFinalFields { private final String a; From 55115a5ca259787ce8d4deb7952d198f50591f92 Mon Sep 17 00:00:00 2001 From: HiFromAjay Date: Fri, 11 Jun 2021 10:04:32 -0600 Subject: [PATCH 09/90] Test cases for testing the exceptional behavior of get, getAsBoolean, getAsDouble, getAsInt, getAsJsonArray, getAsJsonObject, getAsLong, and getAsString methods of JsonArray class. These test cases, which we wrote according to the specified behavior of each method, that helped us in identifying the documentation bugs in JsonArray and JsonElement classes, which we submitted issues for (Issue #1908). Note that we have adapted these test cases based on similar tests from the JSON-java project (https://github.com/stleary/JSON-java). --- .../java/com/google/gson/JsonArrayTest.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/gson/src/test/java/com/google/gson/JsonArrayTest.java b/gson/src/test/java/com/google/gson/JsonArrayTest.java index b77d6f1b44..7ad4f5de8e 100644 --- a/gson/src/test/java/com/google/gson/JsonArrayTest.java +++ b/gson/src/test/java/com/google/gson/JsonArrayTest.java @@ -20,6 +20,9 @@ import com.google.gson.common.MoreAsserts; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + /** * @author Jesse Wilson */ @@ -99,4 +102,65 @@ public void testDeepCopy() { assertEquals(1, original.get(0).getAsJsonArray().size()); assertEquals(0, copy.get(0).getAsJsonArray().size()); } + + public void testFailedGetArrayValues() { + String arrayStr = "[" + "true," + "false," + "\"true\"," + "\"false\"," + "\"hello\"," + "23.45e-4," + "\"23.45\"," + "42," + "\"43\"," + "[" + "\"world\"" + "]," + "{" + "\"key1\":\"value1\"," + "\"key2\":\"value2\"," + "\"key3\":\"value3\"," + "\"key4\":\"value4\"" + "}," + "0," + "\"-1\"" + "]"; + JsonArray jsonArray = (JsonArray) JsonParser.parseString(arrayStr); + try { + jsonArray.get(10).getAsBoolean(); + assertTrue("expected getBoolean to fail", false); + } catch (UnsupportedOperationException e) { + assertEquals("Expected an exception message", + "JsonObject",e.getMessage()); + } + try { + jsonArray.get(-1); + assertTrue("expected get to fail", false); + } catch (IndexOutOfBoundsException e) { + assertEquals("Expected an exception message", + "Index -1 out of bounds for length 13",e.getMessage()); + } + try { + jsonArray.get(4).getAsDouble(); + assertTrue("expected getDouble to fail", false); + } catch (NumberFormatException e) { + assertEquals("Expected an exception message", + "For input string: \"hello\"",e.getMessage()); + } + try { + jsonArray.get(4).getAsInt(); + assertTrue("expected getInt to fail", false); + } catch (NumberFormatException e) { + assertEquals("Expected an exception message", + "For input string: \"hello\"",e.getMessage()); + } + try { + jsonArray.get(4).getAsJsonArray(); + assertTrue("expected getJSONArray to fail", false); + } catch (IllegalStateException e) { + assertEquals("Expected an exception message", + "Not a JSON Array: \"hello\"",e.getMessage()); + } + try { + jsonArray.get(4).getAsJsonObject(); + assertTrue("expected getJSONObject to fail", false); + } catch (IllegalStateException e) { + assertEquals("Expected an exception message", + "Not a JSON Object: \"hello\"",e.getMessage()); + } + try { + jsonArray.get(4).getAsLong(); + assertTrue("expected getLong to fail", false); + } catch (NumberFormatException e) { + assertEquals("Expected an exception message", + "For input string: \"hello\"",e.getMessage()); + } + try { + jsonArray.get(10).getAsString(); + assertTrue("expected getString to fail", false); + } catch (UnsupportedOperationException e) { + assertEquals("Expected an exception message", + "JsonObject",e.getMessage()); + } + } } From 2d1981d39bfcadfeac553582494abaec2fc5d737 Mon Sep 17 00:00:00 2001 From: HiFromAjay Date: Mon, 14 Jun 2021 14:31:14 -0600 Subject: [PATCH 10/90] modify test cases for testing the exceptional behavior of get... methods [use fail(...), use JsonArray methods, remove unused values, formatting, #1909, #1908] --- .../java/com/google/gson/JsonArrayTest.java | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/gson/src/test/java/com/google/gson/JsonArrayTest.java b/gson/src/test/java/com/google/gson/JsonArrayTest.java index 7ad4f5de8e..86ef03c889 100644 --- a/gson/src/test/java/com/google/gson/JsonArrayTest.java +++ b/gson/src/test/java/com/google/gson/JsonArrayTest.java @@ -20,8 +20,8 @@ import com.google.gson.common.MoreAsserts; +import static org.junit.Assert.*; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; /** * @author Jesse Wilson @@ -104,63 +104,66 @@ public void testDeepCopy() { } public void testFailedGetArrayValues() { - String arrayStr = "[" + "true," + "false," + "\"true\"," + "\"false\"," + "\"hello\"," + "23.45e-4," + "\"23.45\"," + "42," + "\"43\"," + "[" + "\"world\"" + "]," + "{" + "\"key1\":\"value1\"," + "\"key2\":\"value2\"," + "\"key3\":\"value3\"," + "\"key4\":\"value4\"" + "}," + "0," + "\"-1\"" + "]"; - JsonArray jsonArray = (JsonArray) JsonParser.parseString(arrayStr); + JsonArray jsonArray = new JsonArray(); + jsonArray.add(JsonParser.parseString("{" + "\"key1\":\"value1\"," + "\"key2\":\"value2\"," + "\"key3\":\"value3\"," + "\"key4\":\"value4\"" + "}")); try { - jsonArray.get(10).getAsBoolean(); - assertTrue("expected getBoolean to fail", false); + jsonArray.getAsBoolean(); + fail("expected getBoolean to fail"); } catch (UnsupportedOperationException e) { assertEquals("Expected an exception message", - "JsonObject",e.getMessage()); + "JsonObject", e.getMessage()); } try { jsonArray.get(-1); - assertTrue("expected get to fail", false); + fail("expected get to fail"); } catch (IndexOutOfBoundsException e) { assertEquals("Expected an exception message", - "Index -1 out of bounds for length 13",e.getMessage()); + "Index -1 out of bounds for length 1", e.getMessage()); } try { - jsonArray.get(4).getAsDouble(); - assertTrue("expected getDouble to fail", false); - } catch (NumberFormatException e) { + jsonArray.getAsString(); + fail("expected getString to fail"); + } catch (UnsupportedOperationException e) { assertEquals("Expected an exception message", - "For input string: \"hello\"",e.getMessage()); + "JsonObject", e.getMessage()); } + + jsonArray.remove(0); + jsonArray.add("hello"); try { - jsonArray.get(4).getAsInt(); - assertTrue("expected getInt to fail", false); + jsonArray.getAsDouble(); + fail("expected getDouble to fail"); } catch (NumberFormatException e) { assertEquals("Expected an exception message", - "For input string: \"hello\"",e.getMessage()); + "For input string: \"hello\"", e.getMessage()); } try { - jsonArray.get(4).getAsJsonArray(); - assertTrue("expected getJSONArray to fail", false); - } catch (IllegalStateException e) { + jsonArray.getAsInt(); + fail("expected getInt to fail"); + } catch (NumberFormatException e) { assertEquals("Expected an exception message", - "Not a JSON Array: \"hello\"",e.getMessage()); + "For input string: \"hello\"", e.getMessage()); } try { - jsonArray.get(4).getAsJsonObject(); - assertTrue("expected getJSONObject to fail", false); + jsonArray.get(0).getAsJsonArray(); + fail("expected getJSONArray to fail"); } catch (IllegalStateException e) { assertEquals("Expected an exception message", - "Not a JSON Object: \"hello\"",e.getMessage()); + "Not a JSON Array: \"hello\"", e.getMessage()); } try { - jsonArray.get(4).getAsLong(); - assertTrue("expected getLong to fail", false); - } catch (NumberFormatException e) { + jsonArray.getAsJsonObject(); + fail("expected getJSONObject to fail"); + } catch (IllegalStateException e) { assertEquals("Expected an exception message", - "For input string: \"hello\"",e.getMessage()); + "Not a JSON Object: [\"hello\"]", e.getMessage()); } try { - jsonArray.get(10).getAsString(); - assertTrue("expected getString to fail", false); - } catch (UnsupportedOperationException e) { + jsonArray.getAsLong(); + fail("expected getLong to fail"); + } catch (NumberFormatException e) { assertEquals("Expected an exception message", - "JsonObject",e.getMessage()); + "For input string: \"hello\"", e.getMessage()); } } } From 425cb25549ae83082b5e1ba4dfbc3bb635a15faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Mon, 2 Aug 2021 17:33:10 -0700 Subject: [PATCH 11/90] Adjust some minor details of #1391. Use two-space indentation for the new test. Use standard Google import style. Supply missing type argument for `TypeVariable`. --- .../com/google/gson/internal/$Gson$Types.java | 6 +- .../ReusedTypeVariablesFullyResolveTest.java | 57 ++++++++++--------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java index 53985bc30a..a708dc055b 100644 --- a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java +++ b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java @@ -339,13 +339,13 @@ public static Type[] getMapKeyAndValueTypes(Type context, Class contextRawTyp } public static Type resolve(Type context, Class contextRawType, Type toResolve) { - return resolve(context, contextRawType, toResolve, new HashMap()); + return resolve(context, contextRawType, toResolve, new HashMap, Type>()); } private static Type resolve(Type context, Class contextRawType, Type toResolve, - Map visitedTypeVariables) { + Map, Type> visitedTypeVariables) { // this implementation is made a little more complicated in an attempt to avoid object-creation - TypeVariable resolving = null; + TypeVariable resolving = null; while (true) { if (toResolve instanceof TypeVariable) { TypeVariable typeVariable = (TypeVariable) toResolve; diff --git a/gson/src/test/java/com/google/gson/functional/ReusedTypeVariablesFullyResolveTest.java b/gson/src/test/java/com/google/gson/functional/ReusedTypeVariablesFullyResolveTest.java index e3ddd840e9..10c7c6db1d 100644 --- a/gson/src/test/java/com/google/gson/functional/ReusedTypeVariablesFullyResolveTest.java +++ b/gson/src/test/java/com/google/gson/functional/ReusedTypeVariablesFullyResolveTest.java @@ -1,16 +1,17 @@ package com.google.gson.functional; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.junit.Before; import org.junit.Test; - import java.util.Collection; import java.util.Iterator; import java.util.Set; -import static org.junit.Assert.*; - /** * This test covers the scenario described in #1390 where a type variable needs to be used * by a type definition multiple times. Both type variable references should resolve to the @@ -18,37 +19,37 @@ */ public class ReusedTypeVariablesFullyResolveTest { - private Gson gson; + private Gson gson; - @Before - public void setUp() { - gson = new GsonBuilder().create(); - } + @Before + public void setUp() { + gson = new GsonBuilder().create(); + } - @SuppressWarnings("ConstantConditions") // The instances were being unmarshaled as Strings instead of TestEnums - @Test - public void testGenericsPreservation() { - TestEnumSetCollection withSet = gson.fromJson("{\"collection\":[\"ONE\",\"THREE\"]}", TestEnumSetCollection.class); - Iterator iterator = withSet.collection.iterator(); - assertNotNull(withSet); - assertNotNull(withSet.collection); - assertEquals(2, withSet.collection.size()); - TestEnum first = iterator.next(); - TestEnum second = iterator.next(); + @SuppressWarnings("ConstantConditions") // The instances were being unmarshaled as Strings instead of TestEnums + @Test + public void testGenericsPreservation() { + TestEnumSetCollection withSet = gson.fromJson("{\"collection\":[\"ONE\",\"THREE\"]}", TestEnumSetCollection.class); + Iterator iterator = withSet.collection.iterator(); + assertNotNull(withSet); + assertNotNull(withSet.collection); + assertEquals(2, withSet.collection.size()); + TestEnum first = iterator.next(); + TestEnum second = iterator.next(); - assertTrue(first instanceof TestEnum); - assertTrue(second instanceof TestEnum); - } + assertTrue(first instanceof TestEnum); + assertTrue(second instanceof TestEnum); + } - enum TestEnum { ONE, TWO, THREE } + enum TestEnum { ONE, TWO, THREE } - private static class TestEnumSetCollection extends SetCollection {} + private static class TestEnumSetCollection extends SetCollection {} - private static class SetCollection extends BaseCollection> {} + private static class SetCollection extends BaseCollection> {} - private static class BaseCollection> - { - public C collection; - } + private static class BaseCollection> + { + public C collection; + } } From 68f99f2440e93b1f80b241ea575929ffa79b9513 Mon Sep 17 00:00:00 2001 From: YOUNG CHA Date: Fri, 22 Mar 2019 17:11:11 +0900 Subject: [PATCH 12/90] Make EnumTypeAdapter friendly with obfuscation When enum value was obfuscated by proguard, EnumTypeAdapter raise NoSuchFieldException even if apply SerializedName annotation. Because EnumTypeAdapter cannot find obfuscated enum constant field with its name. --- .../google/gson/internal/bind/TypeAdapters.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index 354ce5a1fb..f44e056a78 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -17,6 +17,7 @@ package com.google.gson.internal.bind; import java.io.IOException; +import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; @@ -776,9 +777,14 @@ private static final class EnumTypeAdapter> extends TypeAdapte public EnumTypeAdapter(Class classOfT) { try { - for (T constant : classOfT.getEnumConstants()) { + for (Field field : classOfT.getDeclaredFields()) { + if (!field.isEnumConstant()) { + continue; + } + field.setAccessible(true); + T constant = (T)(field.get(null)); String name = constant.name(); - SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class); + SerializedName annotation = field.getAnnotation(SerializedName.class); if (annotation != null) { name = annotation.value(); for (String alternate : annotation.alternate()) { @@ -788,7 +794,11 @@ public EnumTypeAdapter(Class classOfT) { nameToConstant.put(name, constant); constantToName.put(constant, name); } - } catch (NoSuchFieldException e) { + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (NullPointerException e) { + throw new AssertionError(e); + } catch (ExceptionInInitializerError e) { throw new AssertionError(e); } } From 94f894cf44bb908c1dc9b5d7f0a10185a80dc7f8 Mon Sep 17 00:00:00 2001 From: YOUNG CHA Date: Mon, 25 Mar 2019 11:37:09 +0900 Subject: [PATCH 13/90] Add testcase for obfuscated enum class --- gson/pom.xml | 87 +++++++++++++++++++ .../functional/EnumWithObfuscatedTest.java | 60 +++++++++++++ gson/testcases-proguard.conf | 20 +++++ 3 files changed, 167 insertions(+) create mode 100644 gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java create mode 100644 gson/testcases-proguard.conf diff --git a/gson/pom.xml b/gson/pom.xml index 83f52b3a29..bc5e8806a1 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -16,6 +16,12 @@ junit test + + com.github.wvengen + proguard-maven-plugin + 2.0.14 + test + @@ -69,6 +75,87 @@ + + com.coderplus.maven.plugins + copy-rename-maven-plugin + 1.0 + + + pre-obfuscate-class + process-test-classes + + rename + + + + + ${project.build.directory}/test-classes/com/google/gson/functional/EnumWithObfuscatedTest.class + ${project.build.directory}/test-classes-obfuscated-injar/com/google/gson/functional/EnumWithObfuscatedTest.class + + + ${project.build.directory}/test-classes/com/google/gson/functional/EnumWithObfuscatedTest$Gender.class + ${project.build.directory}/test-classes-obfuscated-injar/com/google/gson/functional/EnumWithObfuscatedTest$Gender.class + + + + + + + + com.github.wvengen + proguard-maven-plugin + + + process-test-classes + proguard + + + + 6.0.2 + true + test-classes-obfuscated-injar + test-classes-obfuscated-outjar + **/*.class + ${basedir}/testcases-proguard.conf + + ${project.build.directory}/classes + ${java.home}/jmods/java.base.jmod + + + + + net.sf.proguard + proguard-base + 6.0.2 + runtime + + + + + maven-resources-plugin + 2.7 + + + post-obfuscate-class + process-test-classes + + copy-resources + + + ${project.build.directory}/test-classes/com/google/gson/functional + + + ${project.build.directory}/test-classes-obfuscated-outjar/classes/classes/com/google/gson/functional + + EnumWithObfuscatedTest.class + EnumWithObfuscatedTest$Gender.class + + + + + + + diff --git a/gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java b/gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java new file mode 100644 index 0000000000..a829b07349 --- /dev/null +++ b/gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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 com.google.gson.functional; + +import java.lang.reflect.Field; + +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; + +import junit.framework.TestCase; + +/** + * Functional tests for enums with Proguard. + * + * @author Young Cha + */ +public class EnumWithObfuscatedTest extends TestCase { + private Gson gson; + + @Override + protected void setUp() throws Exception { + super.setUp(); + gson = new Gson(); + } + + public enum Gender { + @SerializedName("MAIL") + MALE, + + @SerializedName("FEMAIL") + FEMALE + } + + public void testEnumClassWithObfuscated() { + for (Gender enumConstant: Gender.class.getEnumConstants()) { + try { + Gender.class.getField(enumConstant.name()); + fail("Enum is not obfuscated"); + } catch (NoSuchFieldException ignore) { + } + } + + assertEquals(Gender.MALE, gson.fromJson("\"MAIL\"", Gender.class)); + assertEquals("\"MAIL\"", gson.toJson(Gender.MALE, Gender.class)); + } +} diff --git a/gson/testcases-proguard.conf b/gson/testcases-proguard.conf new file mode 100644 index 0000000000..d42da0abef --- /dev/null +++ b/gson/testcases-proguard.conf @@ -0,0 +1,20 @@ +# Options from Android Gradle Plugins +# https://android.googlesource.com/platform/tools/base/+/refs/heads/studio-master-dev/build-system/gradle-core/src/main/resources/com/android/build/gradle +-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* +-optimizationpasses 5 +-allowaccessmodification +-keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep enum com.google.gson.functional.EnumWithObfuscatedTest$Gender +-keep class com.google.gson.functional.EnumWithObfuscatedTest { + public void test*(); + protected void setUp(); +} + +-dontwarn com.google.gson.functional.EnumWithObfuscatedTest +-dontwarn junit.framework.TestCase + From 7988fbfa9047f4373bd382efb1b5add660e28cfd Mon Sep 17 00:00:00 2001 From: YOUNG HO CHA Date: Thu, 10 Sep 2020 20:13:41 +0900 Subject: [PATCH 14/90] Update proguard plugin to support Java 11 compiler --- gson/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gson/pom.xml b/gson/pom.xml index bc5e8806a1..3ecd69a193 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -19,7 +19,7 @@ com.github.wvengen proguard-maven-plugin - 2.0.14 + 2.3.1 test @@ -111,7 +111,7 @@ - 6.0.2 + 6.2.2 true test-classes-obfuscated-injar test-classes-obfuscated-outjar @@ -126,7 +126,7 @@ net.sf.proguard proguard-base - 6.0.2 + 6.2.2 runtime From 1406477d0cd37e8d5d04e619ac349f0a6da614e3 Mon Sep 17 00:00:00 2001 From: YOUNG HO CHA Date: Thu, 10 Sep 2020 20:26:10 +0900 Subject: [PATCH 15/90] Fix post-obfuscate-class task to include obfuscated test classes --- gson/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gson/pom.xml b/gson/pom.xml index 3ecd69a193..baf919e30c 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -145,7 +145,7 @@ ${project.build.directory}/test-classes/com/google/gson/functional - ${project.build.directory}/test-classes-obfuscated-outjar/classes/classes/com/google/gson/functional + ${project.build.directory}/test-classes-obfuscated-outjar/com/google/gson/functional EnumWithObfuscatedTest.class EnumWithObfuscatedTest$Gender.class From e99a4b1cb721d8153c4cf9659571fe7ea352799b Mon Sep 17 00:00:00 2001 From: YOUNG HO CHA Date: Sat, 12 Sep 2020 13:35:15 +0900 Subject: [PATCH 16/90] Move testcases-proguard.conf into gson/src/test/resources --- gson/pom.xml | 2 +- gson/{ => src/test/resources}/testcases-proguard.conf | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename gson/{ => src/test/resources}/testcases-proguard.conf (100%) diff --git a/gson/pom.xml b/gson/pom.xml index baf919e30c..14089475a0 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -116,7 +116,7 @@ test-classes-obfuscated-injar test-classes-obfuscated-outjar **/*.class - ${basedir}/testcases-proguard.conf + ${basedir}/src/test/resources/testcases-proguard.conf ${project.build.directory}/classes ${java.home}/jmods/java.base.jmod diff --git a/gson/testcases-proguard.conf b/gson/src/test/resources/testcases-proguard.conf similarity index 100% rename from gson/testcases-proguard.conf rename to gson/src/test/resources/testcases-proguard.conf From 92a98dab02891af20913c07b4172614ca81b64b6 Mon Sep 17 00:00:00 2001 From: YOUNG HO CHA Date: Sat, 12 Sep 2020 13:36:14 +0900 Subject: [PATCH 17/90] Removed unused import --- .../java/com/google/gson/functional/EnumWithObfuscatedTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java b/gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java index a829b07349..8ad206a23a 100644 --- a/gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java +++ b/gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java @@ -16,8 +16,6 @@ package com.google.gson.functional; -import java.lang.reflect.Field; - import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; From 6ac9f7d8400851fa3d0a136817e880126de9840b Mon Sep 17 00:00:00 2001 From: YOUNG HO CHA Date: Sat, 12 Sep 2020 13:40:09 +0900 Subject: [PATCH 18/90] Suppress unchecked type cast warning --- .../main/java/com/google/gson/internal/bind/TypeAdapters.java | 1 + 1 file changed, 1 insertion(+) diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index f44e056a78..f504ad3f8a 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -782,6 +782,7 @@ public EnumTypeAdapter(Class classOfT) { continue; } field.setAccessible(true); + @SuppressWarnings("unchecked") T constant = (T)(field.get(null)); String name = constant.name(); SerializedName annotation = field.getAnnotation(SerializedName.class); From 20720d6a400eaea63c44f088479daea426e2de99 Mon Sep 17 00:00:00 2001 From: YOUNG HO CHA Date: Sat, 12 Sep 2020 13:57:35 +0900 Subject: [PATCH 19/90] Remove unnecessary catch block --- .../main/java/com/google/gson/internal/bind/TypeAdapters.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index f504ad3f8a..e8052a708c 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -797,10 +797,6 @@ public EnumTypeAdapter(Class classOfT) { } } catch (IllegalAccessException e) { throw new AssertionError(e); - } catch (NullPointerException e) { - throw new AssertionError(e); - } catch (ExceptionInInitializerError e) { - throw new AssertionError(e); } } @Override public T read(JsonReader in) throws IOException { From 59a8aedb37ae35d4d2a4306c92efd463fffaaf69 Mon Sep 17 00:00:00 2001 From: YOUNG HO CHA Date: Wed, 4 Aug 2021 12:24:55 +0900 Subject: [PATCH 20/90] Use SecurityManager to read enum fields --- .../com/google/gson/internal/bind/TypeAdapters.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index e8052a708c..04b13ada81 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -24,6 +24,8 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.sql.Timestamp; import java.util.ArrayList; import java.util.BitSet; @@ -777,11 +779,16 @@ private static final class EnumTypeAdapter> extends TypeAdapte public EnumTypeAdapter(Class classOfT) { try { - for (Field field : classOfT.getDeclaredFields()) { + for (final Field field : classOfT.getDeclaredFields()) { if (!field.isEnumConstant()) { continue; } - field.setAccessible(true); + AccessController.doPrivileged(new PrivilegedAction() { + @Override public Void run() { + field.setAccessible(true); + return null; + } + }); @SuppressWarnings("unchecked") T constant = (T)(field.get(null)); String name = constant.name(); From d8c5fcf00bdd3170365e92e43880021031c7d005 Mon Sep 17 00:00:00 2001 From: YOUNG HO CHA Date: Wed, 4 Aug 2021 12:30:07 +0900 Subject: [PATCH 21/90] Fix indentation of EnumWithObfuscatedTest --- .../google/gson/functional/EnumWithObfuscatedTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java b/gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java index 8ad206a23a..080b81fa77 100644 --- a/gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java +++ b/gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java @@ -45,11 +45,11 @@ public enum Gender { public void testEnumClassWithObfuscated() { for (Gender enumConstant: Gender.class.getEnumConstants()) { - try { - Gender.class.getField(enumConstant.name()); - fail("Enum is not obfuscated"); - } catch (NoSuchFieldException ignore) { - } + try { + Gender.class.getField(enumConstant.name()); + fail("Enum is not obfuscated"); + } catch (NoSuchFieldException ignore) { + } } assertEquals(Gender.MALE, gson.fromJson("\"MAIL\"", Gender.class)); From 178b221fa0dfc26bf27331e7ef6da9657bde1fdf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Aug 2021 03:02:56 +0000 Subject: [PATCH 22/90] Bump maven-resources-plugin from 2.7 to 3.2.0 Bumps [maven-resources-plugin](https://github.com/apache/maven-resources-plugin) from 2.7 to 3.2.0. - [Release notes](https://github.com/apache/maven-resources-plugin/releases) - [Commits](https://github.com/apache/maven-resources-plugin/compare/maven-resources-plugin-2.7...maven-resources-plugin-3.2.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-resources-plugin dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- gson/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gson/pom.xml b/gson/pom.xml index 14089475a0..10c7d2fe52 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -133,7 +133,7 @@ maven-resources-plugin - 2.7 + 3.2.0 post-obfuscate-class From b7fce3850d6508ad18500abf3aac1354d277798d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Aug 2021 03:03:00 +0000 Subject: [PATCH 23/90] Bump proguard-maven-plugin from 2.3.1 to 2.4.0 Bumps [proguard-maven-plugin](https://github.com/wvengen/proguard-maven-plugin) from 2.3.1 to 2.4.0. - [Release notes](https://github.com/wvengen/proguard-maven-plugin/releases) - [Changelog](https://github.com/wvengen/proguard-maven-plugin/blob/master/CHANGELOG.md) - [Commits](https://github.com/wvengen/proguard-maven-plugin/commits) --- updated-dependencies: - dependency-name: com.github.wvengen:proguard-maven-plugin dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gson/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gson/pom.xml b/gson/pom.xml index 14089475a0..65f063fe46 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -19,7 +19,7 @@ com.github.wvengen proguard-maven-plugin - 2.3.1 + 2.4.0 test From da2bfd7d1c0462547cb470d992a861987f3cca16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Aug 2021 03:03:02 +0000 Subject: [PATCH 24/90] Bump copy-rename-maven-plugin from 1.0 to 1.0.1 Bumps [copy-rename-maven-plugin](https://github.com/coderplus/copy-rename-maven-plugin) from 1.0 to 1.0.1. - [Release notes](https://github.com/coderplus/copy-rename-maven-plugin/releases) - [Commits](https://github.com/coderplus/copy-rename-maven-plugin/commits) --- updated-dependencies: - dependency-name: com.coderplus.maven.plugins:copy-rename-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gson/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gson/pom.xml b/gson/pom.xml index 14089475a0..ddd0117009 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -78,7 +78,7 @@ com.coderplus.maven.plugins copy-rename-maven-plugin - 1.0 + 1.0.1 pre-obfuscate-class From d3a75cb56937d687a66c8710e40c2da6223db0c3 Mon Sep 17 00:00:00 2001 From: Christoffer Quist Adamsen Date: Thu, 5 Aug 2021 09:18:32 +0200 Subject: [PATCH 25/90] Retain generic signature of TypeToken with R8 version 3.0 and higher --- examples/android-proguard-example/proguard.cfg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/android-proguard-example/proguard.cfg b/examples/android-proguard-example/proguard.cfg index e0e0a97f48..95f31ec6d6 100644 --- a/examples/android-proguard-example/proguard.cfg +++ b/examples/android-proguard-example/proguard.cfg @@ -25,4 +25,8 @@ @com.google.gson.annotations.SerializedName ; } +# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher. +-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken +-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken + ##---------------End: proguard configuration for Gson ---------- From 01ab13f701e6db84bdf37f602ef7af3c8d5c2f35 Mon Sep 17 00:00:00 2001 From: HiFromAjay Date: Thu, 5 Aug 2021 17:23:28 -0600 Subject: [PATCH 26/90] Remove unused imports [#1909, #1908] --- gson/src/test/java/com/google/gson/JsonArrayTest.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/gson/src/test/java/com/google/gson/JsonArrayTest.java b/gson/src/test/java/com/google/gson/JsonArrayTest.java index 86ef03c889..3975ce2c84 100644 --- a/gson/src/test/java/com/google/gson/JsonArrayTest.java +++ b/gson/src/test/java/com/google/gson/JsonArrayTest.java @@ -16,12 +16,8 @@ package com.google.gson; -import junit.framework.TestCase; - import com.google.gson.common.MoreAsserts; - -import static org.junit.Assert.*; -import static org.junit.Assert.assertEquals; +import junit.framework.TestCase; /** * @author Jesse Wilson From f98dabd1e966c97aa88ee74d587eb1ea810e39b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Aug 2021 03:04:02 +0000 Subject: [PATCH 27/90] Bump maven-scm-api from 1.11.2 to 1.11.3 Bumps [maven-scm-api](https://github.com/apache/maven-scm) from 1.11.2 to 1.11.3. - [Release notes](https://github.com/apache/maven-scm/releases) - [Commits](https://github.com/apache/maven-scm/compare/maven-scm-1.11.2...maven-scm-1.11.3) --- updated-dependencies: - dependency-name: org.apache.maven.scm:maven-scm-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2a445fd27b..e75ff2a71d 100644 --- a/pom.xml +++ b/pom.xml @@ -120,7 +120,7 @@ org.apache.maven.scm maven-scm-api - 1.11.2 + 1.11.3 org.apache.maven.scm From 205df01c047ecc03cf217f17e2030852a4ac96be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Aug 2021 03:04:06 +0000 Subject: [PATCH 28/90] Bump maven-scm-provider-gitexe from 1.11.2 to 1.11.3 Bumps maven-scm-provider-gitexe from 1.11.2 to 1.11.3. --- updated-dependencies: - dependency-name: org.apache.maven.scm:maven-scm-provider-gitexe dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2a445fd27b..0860d72f0c 100644 --- a/pom.xml +++ b/pom.xml @@ -125,7 +125,7 @@ org.apache.maven.scm maven-scm-provider-gitexe - 1.11.2 + 1.11.3 From b41030d3dc965eca1b0a0f1c35ce185dd8e80e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Fri, 20 Aug 2021 09:07:19 -0700 Subject: [PATCH 29/90] [maven-release-plugin] prepare release gson-parent-2.8.8 --- gson/pom.xml | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gson/pom.xml b/gson/pom.xml index 99ff049d03..98d4a239fd 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -4,7 +4,7 @@ com.google.code.gson gson-parent - 2.8.8-SNAPSHOT + 2.8.8 gson diff --git a/pom.xml b/pom.xml index c6bb4276c4..cd3348d32c 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.google.code.gson gson-parent - 2.8.8-SNAPSHOT + 2.8.8 pom Gson Parent @@ -31,7 +31,7 @@ https://github.com/google/gson/ scm:git:https://github.com/google/gson.git scm:git:git@github.com:google/gson.git - HEAD + gson-parent-2.8.8 From b2f661166f788185d5c3e5b588d0951c52ec2119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Fri, 20 Aug 2021 09:07:21 -0700 Subject: [PATCH 30/90] [maven-release-plugin] prepare for next development iteration --- gson/pom.xml | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gson/pom.xml b/gson/pom.xml index 98d4a239fd..58d6498900 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -4,7 +4,7 @@ com.google.code.gson gson-parent - 2.8.8 + 2.8.9-SNAPSHOT gson diff --git a/pom.xml b/pom.xml index cd3348d32c..281f0f5cb0 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.google.code.gson gson-parent - 2.8.8 + 2.8.9-SNAPSHOT pom Gson Parent @@ -31,7 +31,7 @@ https://github.com/google/gson/ scm:git:https://github.com/google/gson.git scm:git:git@github.com:google/gson.git - gson-parent-2.8.8 + HEAD From 03be83591467afc368608e80a03f9d6a0e9720fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Fri, 20 Aug 2021 10:02:13 -0700 Subject: [PATCH 31/90] Update change log, and version numbers in documentation. --- CHANGELOG.md | 6 ++++++ README.md | 4 ++-- UserGuide.md | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98aa3ab0de..4f58bb676e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Change Log ========== +## Version 2.8.8 + +* Fixed issue with recursive types (#1390). +* Better behaviour with Java 9+ and `Unsafe` if there is a security manager (#1712). +* `EnumTypeAdapter` now works better when ProGuard has obfuscated enum fields (#1495). + ## Version 2.8.7 * Fixed `ISO8601UtilsTest` failing on systems with UTC+X. diff --git a/README.md b/README.md index 22ffdae567..7a003fbe2a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ There are a few open-source projects that can convert Java objects to JSON. Howe Gradle: ```gradle dependencies { - implementation 'com.google.code.gson:gson:2.8.7' + implementation 'com.google.code.gson:gson:2.8.8' } ``` @@ -26,7 +26,7 @@ Maven: com.google.code.gson gson - 2.8.7 + 2.8.8 ``` diff --git a/UserGuide.md b/UserGuide.md index 22d4799d23..5fae53c46d 100644 --- a/UserGuide.md +++ b/UserGuide.md @@ -74,7 +74,7 @@ The Gson instance does not maintain any state while invoking Json operations. So ## Using Gson with Gradle/Android ``` dependencies { - implementation 'com.google.code.gson:gson:2.8.7' + implementation 'com.google.code.gson:gson:2.8.8' } ``` ## Using Gson with Maven @@ -86,7 +86,7 @@ To use Gson with Maven2/3, you can use the Gson version available in Maven Centr com.google.code.gson gson - 2.8.7 + 2.8.8 compile From 62a97023852f879eb5819c3ef0fb7e7fdda51bc7 Mon Sep 17 00:00:00 2001 From: Simon Guerout Date: Fri, 20 Aug 2021 16:02:18 -0400 Subject: [PATCH 32/90] Improve the speed of the JSON_ELEMENT TypeAdapter when the object graph has already been turned into a JsonElement --- .../com/google/gson/internal/bind/JsonTreeReader.java | 9 +++++++++ .../java/com/google/gson/internal/bind/TypeAdapters.java | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java index a223754aed..986c927b21 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java +++ b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java @@ -249,6 +249,15 @@ private void expect(JsonToken expected) throws IOException { return result; } + JsonElement nextJsonElement() throws IOException { + if (peek() == JsonToken.NAME) { + throw new IllegalStateException("Can't turn a name into a JsonElement"); + } + final JsonElement element = (JsonElement) peekStack(); + skipValue(); + return element; + } + @Override public void close() throws IOException { stack = new Object[] { SENTINEL_CLOSED }; stackSize = 1; diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index 6bbd680ab4..81dda90359 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -677,6 +677,10 @@ public void write(JsonWriter out, Locale value) throws IOException { public static final TypeAdapter JSON_ELEMENT = new TypeAdapter() { @Override public JsonElement read(JsonReader in) throws IOException { + if (in instanceof JsonTreeReader) { + return ((JsonTreeReader) in).nextJsonElement(); + } + switch (in.peek()) { case STRING: return new JsonPrimitive(in.nextString()); From ac14b4c197382810601574f1defd995842d7bfd7 Mon Sep 17 00:00:00 2001 From: Simon Guerout Date: Wed, 25 Aug 2021 09:56:01 -0400 Subject: [PATCH 33/90] Make the nextJsonElement more robust Add test cases --- .../gson/internal/bind/JsonTreeReader.java | 10 ++++-- .../internal/bind/JsonElementReaderTest.java | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java index 986c927b21..9c03e8d3b2 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java +++ b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java @@ -250,8 +250,14 @@ private void expect(JsonToken expected) throws IOException { } JsonElement nextJsonElement() throws IOException { - if (peek() == JsonToken.NAME) { - throw new IllegalStateException("Can't turn a name into a JsonElement"); + final JsonToken peeked = peek(); + if ( + peeked == JsonToken.NAME + || peeked == JsonToken.END_ARRAY + || peeked == JsonToken.END_OBJECT + || peeked == JsonToken.END_DOCUMENT + ) { + throw new IllegalStateException("Unexpected " + peeked + " when reading a JsonElement."); } final JsonElement element = (JsonElement) peekStack(); skipValue(); diff --git a/gson/src/test/java/com/google/gson/internal/bind/JsonElementReaderTest.java b/gson/src/test/java/com/google/gson/internal/bind/JsonElementReaderTest.java index 4c4dd8df94..204fb3c3f5 100644 --- a/gson/src/test/java/com/google/gson/internal/bind/JsonElementReaderTest.java +++ b/gson/src/test/java/com/google/gson/internal/bind/JsonElementReaderTest.java @@ -18,6 +18,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; import com.google.gson.stream.JsonToken; import java.io.IOException; import junit.framework.TestCase; @@ -298,6 +299,41 @@ public void testWrongType() throws IOException { reader.endArray(); } + public void testNextJsonElement() throws IOException { + final JsonElement element = JsonParser.parseString("{\"A\": 1, \"B\" : {}, \"C\" : []}"); + JsonTreeReader reader = new JsonTreeReader(element); + reader.beginObject(); + try { + reader.nextJsonElement(); + fail(); + } catch (IllegalStateException expected) { + } + reader.nextName(); + assertEquals(reader.nextJsonElement(), new JsonPrimitive(1)); + reader.nextName(); + reader.beginObject(); + try { + reader.nextJsonElement(); + fail(); + } catch (IllegalStateException expected) { + } + reader.endObject(); + reader.nextName(); + reader.beginArray(); + try { + reader.nextJsonElement(); + fail(); + } catch (IllegalStateException expected) { + } + reader.endArray(); + reader.endObject(); + try { + reader.nextJsonElement(); + fail(); + } catch (IllegalStateException expected) { + } + } + public void testEarlyClose() throws IOException { JsonElement element = JsonParser.parseString("[1, 2, 3]"); JsonTreeReader reader = new JsonTreeReader(element); From 66fd2acdcf7d2c4f38afba3595e509df8651f2c5 Mon Sep 17 00:00:00 2001 From: Simon Guerout Date: Mon, 30 Aug 2021 10:33:16 -0400 Subject: [PATCH 34/90] Fix formatting --- .../com/google/gson/internal/bind/JsonTreeReader.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java index 9c03e8d3b2..60f4296bb0 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java +++ b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java @@ -251,12 +251,10 @@ private void expect(JsonToken expected) throws IOException { JsonElement nextJsonElement() throws IOException { final JsonToken peeked = peek(); - if ( - peeked == JsonToken.NAME - || peeked == JsonToken.END_ARRAY - || peeked == JsonToken.END_OBJECT - || peeked == JsonToken.END_DOCUMENT - ) { + if (peeked == JsonToken.NAME + || peeked == JsonToken.END_ARRAY + || peeked == JsonToken.END_OBJECT + || peeked == JsonToken.END_DOCUMENT) { throw new IllegalStateException("Unexpected " + peeked + " when reading a JsonElement."); } final JsonElement element = (JsonElement) peekStack(); From 1a2e58a42f7eeb011293f5ab4dd40da9f5e39ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Tue, 31 Aug 2021 14:55:07 -0700 Subject: [PATCH 35/90] Remove an unused import. (#1947) --- .../java/com/google/gson/functional/DefaultTypeAdaptersTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java index 0dae4ede22..e2c2b02e08 100644 --- a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java @@ -39,7 +39,6 @@ import java.net.InetAddress; import java.net.URI; import java.net.URL; -import java.sql.Time; import java.sql.Timestamp; import java.text.DateFormat; import java.util.ArrayList; From c8f26dc907515b40dd2ddb471ee1d6cc097d0e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Tue, 31 Aug 2021 15:03:45 -0700 Subject: [PATCH 36/90] Add missing calls when testing for exceptions. (#1948) --- .../google/gson/functional/DefaultTypeAdaptersTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java index e2c2b02e08..f81537f5aa 100644 --- a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java @@ -90,7 +90,9 @@ protected void tearDown() throws Exception { public void testClassSerialization() { try { gson.toJson(String.class); - } catch (UnsupportedOperationException expected) {} + fail(); + } catch (UnsupportedOperationException expected) { + } // Override with a custom type adapter for class. gson = new GsonBuilder().registerTypeAdapter(Class.class, new MyClassTypeAdapter()).create(); assertEquals("\"java.lang.String\"", gson.toJson(String.class)); @@ -99,7 +101,9 @@ public void testClassSerialization() { public void testClassDeserialization() { try { gson.fromJson("String.class", String.class.getClass()); - } catch (UnsupportedOperationException expected) {} + fail(); + } catch (UnsupportedOperationException expected) { + } // Override with a custom type adapter for class. gson = new GsonBuilder().registerTypeAdapter(Class.class, new MyClassTypeAdapter()).create(); assertEquals(String.class, gson.fromJson("java.lang.String", Class.class)); From 4bb67483f926a5a49d173e2fef1be5cfe58f1ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89amonn=20McManus?= Date: Wed, 1 Sep 2021 17:11:48 -0700 Subject: [PATCH 37/90] Update dependencies in proto/pom.xml. (#1949) Also use GeneratedMessageV3 rather than GeneratedMessage, consistent with recent versions of protoc. --- proto/pom.xml | 10 +++++----- .../functional/ProtosWithAnnotationsTest.java | 13 ++++++------- .../ProtosWithComplexAndRepeatedFieldsTest.java | 7 +++---- .../functional/ProtosWithPrimitiveTypesTest.java | 5 ++--- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/proto/pom.xml b/proto/pom.xml index 3ce08bbe17..14fd44ce70 100644 --- a/proto/pom.xml +++ b/proto/pom.xml @@ -55,35 +55,35 @@ com.google.code.gson gson - 2.4 + 2.8.8 compile com.google.protobuf protobuf-java - 2.6.1 + 4.0.0-rc-2 compile com.google.guava guava - 18.0 + 30.1.1-jre compile junit junit - 4.13.1 + 4.13.2 test com.google.truth truth - 0.27 + 1.1.3 test diff --git a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsTest.java b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsTest.java index 2bb6f183ae..0a508f9d6d 100644 --- a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsTest.java +++ b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsTest.java @@ -16,7 +16,7 @@ package com.google.gson.protobuf.functional; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assert_; +import static com.google.common.truth.Truth.assertWithMessage; import com.google.common.base.CaseFormat; import com.google.gson.Gson; @@ -30,8 +30,7 @@ import com.google.gson.protobuf.generated.Bag.ProtoWithAnnotations.InnerMessage; import com.google.gson.protobuf.generated.Bag.ProtoWithAnnotations.InnerMessage.Data; import com.google.gson.protobuf.generated.Bag.ProtoWithAnnotations.InnerMessage.Type; -import com.google.protobuf.GeneratedMessage; - +import com.google.protobuf.GeneratedMessageV3; import junit.framework.TestCase; /** @@ -52,15 +51,15 @@ protected void setUp() throws Exception { .addSerializedNameExtension(Annotations.serializedName) .addSerializedEnumValueExtension(Annotations.serializedValue); gson = new GsonBuilder() - .registerTypeHierarchyAdapter(GeneratedMessage.class, protoTypeAdapter.build()) + .registerTypeHierarchyAdapter(GeneratedMessageV3.class, protoTypeAdapter.build()) .create(); gsonWithEnumNumbers = new GsonBuilder() - .registerTypeHierarchyAdapter(GeneratedMessage.class, protoTypeAdapter + .registerTypeHierarchyAdapter(GeneratedMessageV3.class, protoTypeAdapter .setEnumSerialization(EnumSerialization.NUMBER) .build()) .create(); gsonWithLowerHyphen = new GsonBuilder() - .registerTypeHierarchyAdapter(GeneratedMessage.class, protoTypeAdapter + .registerTypeHierarchyAdapter(GeneratedMessageV3.class, protoTypeAdapter .setFieldNameSerializationFormat(CaseFormat.LOWER_UNDERSCORE, CaseFormat.LOWER_HYPHEN) .build()) .create(); @@ -157,7 +156,7 @@ public void testProtoWithAnnotations_deserializeUnrecognizedEnumValue() { + "}"); try { gson.fromJson(json, InnerMessage.class); - assert_().fail("Should have thrown"); + assertWithMessage("Should have thrown").fail(); } catch (JsonParseException e) { // expected } diff --git a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithComplexAndRepeatedFieldsTest.java b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithComplexAndRepeatedFieldsTest.java index b61d3f509c..8e59d5d1e9 100644 --- a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithComplexAndRepeatedFieldsTest.java +++ b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithComplexAndRepeatedFieldsTest.java @@ -24,8 +24,7 @@ import com.google.gson.protobuf.generated.Bag.ProtoWithDifferentCaseFormat; import com.google.gson.protobuf.generated.Bag.ProtoWithRepeatedFields; import com.google.gson.protobuf.generated.Bag.SimpleProto; -import com.google.protobuf.GeneratedMessage; - +import com.google.protobuf.GeneratedMessageV3; import junit.framework.TestCase; /** @@ -42,7 +41,7 @@ protected void setUp() throws Exception { super.setUp(); gson = new GsonBuilder() - .registerTypeHierarchyAdapter(GeneratedMessage.class, + .registerTypeHierarchyAdapter(GeneratedMessageV3.class, ProtoTypeAdapter.newBuilder() .setEnumSerialization(EnumSerialization.NUMBER) .build()) @@ -50,7 +49,7 @@ protected void setUp() throws Exception { upperCamelGson = new GsonBuilder() .registerTypeHierarchyAdapter( - GeneratedMessage.class, ProtoTypeAdapter.newBuilder() + GeneratedMessageV3.class, ProtoTypeAdapter.newBuilder() .setFieldNameSerializationFormat( CaseFormat.LOWER_UNDERSCORE, CaseFormat.UPPER_CAMEL) .build()) diff --git a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithPrimitiveTypesTest.java b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithPrimitiveTypesTest.java index 69b877e60b..2e9d0e1733 100644 --- a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithPrimitiveTypesTest.java +++ b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithPrimitiveTypesTest.java @@ -21,8 +21,7 @@ import com.google.gson.protobuf.ProtoTypeAdapter.EnumSerialization; import com.google.gson.protobuf.generated.Bag.SimpleProto; import com.google.protobuf.Descriptors.Descriptor; -import com.google.protobuf.GeneratedMessage; - +import com.google.protobuf.GeneratedMessageV3; import junit.framework.TestCase; public class ProtosWithPrimitiveTypesTest extends TestCase { @@ -32,7 +31,7 @@ public class ProtosWithPrimitiveTypesTest extends TestCase { protected void setUp() throws Exception { super.setUp(); gson = new GsonBuilder().registerTypeHierarchyAdapter( - GeneratedMessage.class, ProtoTypeAdapter.newBuilder() + GeneratedMessageV3.class, ProtoTypeAdapter.newBuilder() .setEnumSerialization(EnumSerialization.NUMBER) .build()) .create(); From 4f2aeaa2881aaa92bd4a026f9aa140f5196158cd Mon Sep 17 00:00:00 2001 From: Nikita Novik Date: Mon, 6 Sep 2021 20:35:43 +0300 Subject: [PATCH 38/90] Add license note to GSON POM file. (#1951) * Add license note to GSON POM file. --- gson/pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gson/pom.xml b/gson/pom.xml index 58d6498900..dcdbe84637 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -10,6 +10,13 @@ gson Gson + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + + + junit From 9484297fbbc357816af78ac60b776e9da05787b3 Mon Sep 17 00:00:00 2001 From: Z <32814152+SuperZ2017@users.noreply.github.com> Date: Tue, 7 Sep 2021 23:00:11 +0800 Subject: [PATCH 39/90] update UserGuide.md (#1954) Id Class does not have get method, we should new a instance. --- UserGuide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UserGuide.md b/UserGuide.md index 5fae53c46d..c5d4710b9e 100644 --- a/UserGuide.md +++ b/UserGuide.md @@ -429,7 +429,7 @@ class IdInstanceCreator implements InstanceCreator> { public Id createInstance(Type type) { Type[] typeParameters = ((ParameterizedType)type).getActualTypeArguments(); Type idType = typeParameters[0]; // Id has only one parameterized type T - return Id.get((Class)idType, 0L); + return new Id((Class)idType, 0L); } } ``` From ebe4b581ddc5adc0e3cab0d3a7f214691ecebf24 Mon Sep 17 00:00:00 2001 From: Z <32814152+SuperZ2017@users.noreply.github.com> Date: Tue, 7 Sep 2021 23:02:04 +0800 Subject: [PATCH 40/90] update RawCollectionsExample.java (#1953) use static method instead deprecated method --- .../extras/examples/rawcollections/RawCollectionsExample.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extras/src/main/java/com/google/gson/extras/examples/rawcollections/RawCollectionsExample.java b/extras/src/main/java/com/google/gson/extras/examples/rawcollections/RawCollectionsExample.java index bd7c2d24d0..9ea350cae8 100644 --- a/extras/src/main/java/com/google/gson/extras/examples/rawcollections/RawCollectionsExample.java +++ b/extras/src/main/java/com/google/gson/extras/examples/rawcollections/RawCollectionsExample.java @@ -45,8 +45,7 @@ public static void main(String[] args) { collection.add(new Event("GREETINGS", "guest")); String json = gson.toJson(collection); System.out.println("Using Gson.toJson() on a raw collection: " + json); - JsonParser parser = new JsonParser(); - JsonArray array = parser.parse(json).getAsJsonArray(); + JsonArray array = JsonParser.parseString(json).getAsJsonArray(); String message = gson.fromJson(array.get(0), String.class); int number = gson.fromJson(array.get(1), int.class); Event event = gson.fromJson(array.get(2), Event.class); From b82c76717b91b4f5ba79dd54383f8e59829a4597 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Sep 2021 06:14:06 -0700 Subject: [PATCH 41/90] Bump maven-javadoc-plugin from 3.3.0 to 3.3.1 (#1956) Bumps [maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.3.0 to 3.3.1. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.3.0...maven-javadoc-plugin-3.3.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 281f0f5cb0..f36f0b0574 100644 --- a/pom.xml +++ b/pom.xml @@ -97,7 +97,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.3.0 + 3.3.1 org.apache.maven.plugins From f1f90313fc22c122a28716e7ecac1543e6fa253e Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sat, 18 Sep 2021 02:19:50 +0200 Subject: [PATCH 42/90] Improve Maven build (#1964) - Specify missing plugin versions - Fix or suppress ProGuard notes and warnings --- gson/pom.xml | 31 ++++++++++++------- .../test/resources/testcases-proguard.conf | 5 ++- pom.xml | 1 + 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/gson/pom.xml b/gson/pom.xml index dcdbe84637..856908ae7c 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -10,27 +10,25 @@ gson Gson + + 7.1.1 + + Apache-2.0 https://www.apache.org/licenses/LICENSE-2.0.txt - + junit junit test - - com.github.wvengen - proguard-maven-plugin - 2.4.0 - test - - + @@ -111,14 +109,17 @@ com.github.wvengen proguard-maven-plugin + 2.4.0 process-test-classes - proguard + + proguard + - 6.2.2 + ${proguardVersion} true test-classes-obfuscated-injar test-classes-obfuscated-outjar @@ -131,9 +132,15 @@ - net.sf.proguard + com.guardsquare + proguard-core + ${proguardVersion} + runtime + + + com.guardsquare proguard-base - 6.2.2 + ${proguardVersion} runtime diff --git a/gson/src/test/resources/testcases-proguard.conf b/gson/src/test/resources/testcases-proguard.conf index d42da0abef..19def61b00 100644 --- a/gson/src/test/resources/testcases-proguard.conf +++ b/gson/src/test/resources/testcases-proguard.conf @@ -3,6 +3,8 @@ -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* -optimizationpasses 5 -allowaccessmodification +# On Windows mixed case class names might cause problems +-dontusemixedcaseclassnames -keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod -keepclassmembers enum * { public static **[] values(); @@ -17,4 +19,5 @@ -dontwarn com.google.gson.functional.EnumWithObfuscatedTest -dontwarn junit.framework.TestCase - +# Ignore notes about duplicate JDK classes +-dontnote module-info,jdk.internal.** diff --git a/pom.xml b/pom.xml index f36f0b0574..19a6569823 100644 --- a/pom.xml +++ b/pom.xml @@ -102,6 +102,7 @@ org.apache.maven.plugins maven-jar-plugin + 3.2.0 org.apache.felix From 26a1928277f7eba70609f02697509ba9258dd8ef Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sat, 18 Sep 2021 02:21:37 +0200 Subject: [PATCH 43/90] Fix `RuntimeTypeAdapterFactory` depending on internal `Streams` class (#1959) * Fix RuntimeTypeAdapterFactory depending on internal Streams class * Clean up gson-extras project, make it Maven module of gson-parent * Remove broken test from GraphAdapterBuilderTest --- extras/pom.xml | 176 +++--------------- .../RuntimeTypeAdapterFactory.java | 9 +- .../gson/graph/GraphAdapterBuilderTest.java | 36 ++-- pom.xml | 3 +- 4 files changed, 55 insertions(+), 169 deletions(-) diff --git a/extras/pom.xml b/extras/pom.xml index 9b74d1a19a..91da708bae 100644 --- a/extras/pom.xml +++ b/extras/pom.xml @@ -1,47 +1,37 @@ - + 4.0.0 - com.google.code.gson + + com.google.code.gson + gson-parent + 2.8.9-SNAPSHOT + + gson-extras jar 1.0-SNAPSHOT 2008 Gson Extras - - org.sonatype.oss - oss-parent - 9 - - http://code.google.com/p/google-gson/ Google Gson grab bag of utilities, type adapters, etc. - - UTF-8 - + - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt - - scm:svn:http://google-gson.googlecode.com/svn/trunk/extras - scm:svn:https://google-gson.googlecode.com/svn/trunk/extras - http://google-gson.codegoogle.com/svn/trunk/extras - - - Google Code Issue Tracking - http://code.google.com/p/google-gson/issues/list - + Google, Inc. - http://www.google.com + https://www.google.com + com.google.code.gson gson - 2.7 - compile + ${project.parent.version} javax.annotation @@ -51,130 +41,26 @@ junit junit - 4.13.1 test - - - - release-sign-artifacts - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - - - + - package - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - 1.6 - 1.6 - - - - org.apache.maven.plugins - maven-jar-plugin - 3.0.2 - - - package - - jar - - - - - - false - - - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - attach-sources - verify - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.10.4 - - - attach-javadocs - - jar - - - - - - http://download.oracle.com/javase/1.5.0/docs/api/ - - true - public - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.10 - - true - true - - ../eclipse-ws/ - - - file:///${basedir}/../lib/gson-formatting-styles.xml - - - - - org.apache.maven.plugins - maven-release-plugin - - - -DenableCiProfile=true - https://google-gson.googlecode.com/svn/tags/ - - - + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.0.0-M1 + + + true + + + + + Inderjeet Singh diff --git a/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java b/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java index c496491ac6..c11ca83690 100644 --- a/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java +++ b/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java @@ -27,7 +27,6 @@ import com.google.gson.JsonPrimitive; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; -import com.google.gson.internal.Streams; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; @@ -204,11 +203,13 @@ public RuntimeTypeAdapterFactory registerSubtype(Class type) { return registerSubtype(type, type.getSimpleName()); } + @Override public TypeAdapter create(Gson gson, TypeToken type) { if (type.getRawType() != baseType) { return null; } + final TypeAdapter jsonElementAdapter = gson.getAdapter(JsonElement.class); final Map> labelToDelegate = new LinkedHashMap>(); final Map, TypeAdapter> subtypeToDelegate @@ -221,7 +222,7 @@ public TypeAdapter create(Gson gson, TypeToken type) { return new TypeAdapter() { @Override public R read(JsonReader in) throws IOException { - JsonElement jsonElement = Streams.parse(in); + JsonElement jsonElement = jsonElementAdapter.read(in); JsonElement labelJsonElement; if (maintainType) { labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); @@ -255,7 +256,7 @@ public TypeAdapter create(Gson gson, TypeToken type) { JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); if (maintainType) { - Streams.write(jsonObject, out); + jsonElementAdapter.write(out, jsonObject); return; } @@ -270,7 +271,7 @@ public TypeAdapter create(Gson gson, TypeToken type) { for (Map.Entry e : jsonObject.entrySet()) { clone.add(e.getKey(), e.getValue()); } - Streams.write(clone, out); + jsonElementAdapter.write(out, clone); } }.nullSafe(); } diff --git a/extras/src/test/java/com/google/gson/graph/GraphAdapterBuilderTest.java b/extras/src/test/java/com/google/gson/graph/GraphAdapterBuilderTest.java index 8a1d7cdbfb..43fc6b6991 100644 --- a/extras/src/test/java/com/google/gson/graph/GraphAdapterBuilderTest.java +++ b/extras/src/test/java/com/google/gson/graph/GraphAdapterBuilderTest.java @@ -16,16 +16,22 @@ package com.google.gson.graph; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import junit.framework.TestCase; -public final class GraphAdapterBuilderTest extends TestCase { +import org.junit.Test; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +public final class GraphAdapterBuilderTest { + @Test public void testSerialization() { Roshambo rock = new Roshambo("ROCK"); Roshambo scissors = new Roshambo("SCISSORS"); @@ -46,6 +52,7 @@ public void testSerialization() { gson.toJson(rock).replace('"', '\'')); } + @Test public void testDeserialization() { String json = "{'0x1':{'name':'ROCK','beats':'0x2'}," + "'0x2':{'name':'SCISSORS','beats':'0x3'}," + @@ -66,20 +73,7 @@ public void testDeserialization() { assertSame(rock, paper.beats); } - public void testSerializationDirectSelfReference() { - Roshambo suicide = new Roshambo("SUICIDE"); - suicide.beats = suicide; - - GsonBuilder gsonBuilder = new GsonBuilder(); - new GraphAdapterBuilder() - .addType(Roshambo.class) - .registerOn(gsonBuilder); - Gson gson = gsonBuilder.create(); - - assertEquals("{'0x1':{'name':'SUICIDE','beats':'0x1'}}", - gson.toJson(suicide).replace('"', '\'')); - } - + @Test public void testDeserializationDirectSelfReference() { String json = "{'0x1':{'name':'SUICIDE','beats':'0x1'}}"; @@ -94,6 +88,7 @@ public void testDeserializationDirectSelfReference() { assertSame(suicide, suicide.beats); } + @Test public void testSerializeListOfLists() { Type listOfListsType = new TypeToken>>() {}.getType(); Type listOfAnyType = new TypeToken>() {}.getType(); @@ -113,6 +108,7 @@ public void testSerializeListOfLists() { assertEquals("{'0x1':['0x1','0x2'],'0x2':[]}", json.replace('"', '\'')); } + @Test public void testDeserializeListOfLists() { Type listOfAnyType = new TypeToken>() {}.getType(); Type listOfListsType = new TypeToken>>() {}.getType(); @@ -130,6 +126,7 @@ public void testDeserializeListOfLists() { assertEquals(Collections.emptyList(), listOfLists.get(1)); } + @Test public void testSerializationWithMultipleTypes() { Company google = new Company("Google"); new Employee("Jesse", google); @@ -148,6 +145,7 @@ public void testSerializationWithMultipleTypes() { gson.toJson(google).replace('"', '\'')); } + @Test public void testDeserializationWithMultipleTypes() { GsonBuilder gsonBuilder = new GsonBuilder(); new GraphAdapterBuilder() diff --git a/pom.xml b/pom.xml index 19a6569823..ed6aff84e1 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ gson + extras @@ -41,7 +42,7 @@ - Apache 2.0 + Apache-2.0 https://www.apache.org/licenses/LICENSE-2.0.txt From 6a5e7753624dc2f1619137a7fbf0f89a160debdd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Sep 2021 17:27:30 -0700 Subject: [PATCH 44/90] Bump maven-scm-provider-gitexe from 1.11.3 to 1.12.0 (#1967) Bumps maven-scm-provider-gitexe from 1.11.3 to 1.12.0. --- updated-dependencies: - dependency-name: org.apache.maven.scm:maven-scm-provider-gitexe dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ed6aff84e1..b093161a0e 100644 --- a/pom.xml +++ b/pom.xml @@ -127,7 +127,7 @@ org.apache.maven.scm maven-scm-provider-gitexe - 1.11.3 + 1.12.0 From aa5554e69a2d5ee7e557b3cc1f5d68275fd4c1ab Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sat, 18 Sep 2021 03:12:47 +0200 Subject: [PATCH 45/90] Don't exclude static local classes (#1969) --- gson/src/main/java/com/google/gson/internal/Excluder.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/Excluder.java b/gson/src/main/java/com/google/gson/internal/Excluder.java index 6b83757edb..9e7f322114 100644 --- a/gson/src/main/java/com/google/gson/internal/Excluder.java +++ b/gson/src/main/java/com/google/gson/internal/Excluder.java @@ -173,7 +173,7 @@ public boolean excludeField(Field field, boolean serialize) { return true; } - if (isAnonymousOrLocal(field.getType())) { + if (isAnonymousOrNonStaticLocal(field.getType())) { return true; } @@ -199,7 +199,7 @@ private boolean excludeClassChecks(Class clazz) { return true; } - if (isAnonymousOrLocal(clazz)) { + if (isAnonymousOrNonStaticLocal(clazz)) { return true; } @@ -221,8 +221,8 @@ private boolean excludeClassInStrategy(Class clazz, boolean serialize) { return false; } - private boolean isAnonymousOrLocal(Class clazz) { - return !Enum.class.isAssignableFrom(clazz) + private boolean isAnonymousOrNonStaticLocal(Class clazz) { + return !Enum.class.isAssignableFrom(clazz) && !isStatic(clazz) && (clazz.isAnonymousClass() || clazz.isLocalClass()); } From 3b7835a18b7ce999fe79ad12cd85014642f30968 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Fri, 24 Sep 2021 17:34:16 +0200 Subject: [PATCH 46/90] Switch to GitHub Actions (#1974) --- .github/workflows/build.yml | 18 ++++++++++++++++++ .travis.yml | 20 -------------------- README.md | 2 +- 3 files changed, 19 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..688ecff1a1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,18 @@ +name: Build + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: '11' + cache: 'maven' + - name: Build with Maven + run: mvn --batch-mode --update-snapshots verify diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0c36a2d81c..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: java - -jdk: - - openjdk11 - -install: mvn -f gson install -DskipTests=true -script: mvn -f gson test - -branches: - except: - - gh-pages - -notifications: - email: false - -sudo: false - -cache: - directories: - - $HOME/.m2 diff --git a/README.md b/README.md index 7a003fbe2a..17acc950aa 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Maven: [Gson jar downloads](https://maven-badges.herokuapp.com/maven-central/com.google.code.gson/gson) are available from Maven Central. -[![Build Status](https://travis-ci.org/google/gson.svg?branch=master)](https://travis-ci.org/google/gson) +![Build Status](https://github.com/google/gson/actions/workflows/build.yml/badge.svg) ### Documentation * [API Javadoc](https://www.javadoc.io/doc/com.google.code.gson/gson): Documentation for the current release From b0f3237bb88141635c2cbc449f2cd632cbad20bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Sep 2021 09:59:56 -0700 Subject: [PATCH 47/90] Bump proguard-maven-plugin from 2.4.0 to 2.5.1 (#1980) Bumps [proguard-maven-plugin](https://github.com/wvengen/proguard-maven-plugin) from 2.4.0 to 2.5.1. - [Release notes](https://github.com/wvengen/proguard-maven-plugin/releases) - [Changelog](https://github.com/wvengen/proguard-maven-plugin/blob/master/CHANGELOG.md) - [Commits](https://github.com/wvengen/proguard-maven-plugin/commits) --- updated-dependencies: - dependency-name: com.github.wvengen:proguard-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gson/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gson/pom.xml b/gson/pom.xml index 856908ae7c..76ee9de3a7 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -109,7 +109,7 @@ com.github.wvengen proguard-maven-plugin - 2.4.0 + 2.5.1 process-test-classes From 7cfdf44893b74fb762757c37d14e441e98ab9663 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Wed, 29 Sep 2021 01:35:55 +0200 Subject: [PATCH 48/90] Add GitHub issue templates (#1977) * Add GitHub issue templates * Adjust bug report issue template --- .github/ISSUE_TEMPLATE/bug_report.md | 49 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 4 ++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++++ README.md | 2 +- 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..483fd2effe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,49 @@ +--- +name: Bug report +about: Report a Gson bug. +title: '' +labels: bug +assignees: '' + +--- + +# Gson version + + + +# Java / Android version + + + +# Used tools + +- [ ] Maven; version: +- [ ] Gradle; version: +- [ ] ProGuard (attach the configuration file please); version: +- [ ] ... + +# Description + + + +## Expected behavior + + + +## Actual behavior + + + +# Reproduction steps + + + +1. ... +2. ... + +# Exception stack trace + + +``` + +``` diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..b798788d02 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: Usage question + url: https://stackoverflow.com/questions/tagged/gson + about: Ask usage questions on StackOverflow. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..87ed0f8ee3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Request a feature. ⚠️ Gson is in maintenance mode; large feature requests might be rejected. +title: '' +labels: feature-request +assignees: '' + +--- + +# Problem solved by the feature + + + +# Feature description + + + +# Alternatives / workarounds + + diff --git a/README.md b/README.md index 17acc950aa..a1dd0492ae 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Maven: * [Change log](https://github.com/google/gson/blob/master/CHANGELOG.md): Changes in the recent versions * [Design document](https://github.com/google/gson/blob/master/GsonDesignDocument.md): This document discusses issues we faced while designing Gson. It also includes a comparison of Gson with other Java libraries that can be used for Json conversion -Please use the 'gson' tag on StackOverflow or the [google-gson Google group](https://groups.google.com/group/google-gson) to discuss Gson or to post questions. +Please use the ['gson' tag on StackOverflow](https://stackoverflow.com/questions/tagged/gson) or the [google-gson Google group](https://groups.google.com/group/google-gson) to discuss Gson or to post questions. ### Related Content Created by Third Parties * [Gson Tutorial](https://www.studytrails.com/java/json/java-google-json-introduction/) by `StudyTrails` From 7b9a283a7a5d66878c9be01227b15e631afe2a5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Oct 2021 17:58:02 -0700 Subject: [PATCH 49/90] Bump bnd-maven-plugin from 5.3.0 to 6.0.0 (#1985) Bumps [bnd-maven-plugin](https://github.com/bndtools/bnd) from 5.3.0 to 6.0.0. - [Release notes](https://github.com/bndtools/bnd/releases) - [Changelog](https://github.com/bndtools/bnd/blob/master/docs/ADDING_RELEASE_DOCS.md) - [Commits](https://github.com/bndtools/bnd/commits/6.0.0) --- updated-dependencies: - dependency-name: biz.aQute.bnd:bnd-maven-plugin dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gson/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gson/pom.xml b/gson/pom.xml index 76ee9de3a7..6d1a5eb001 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -45,7 +45,7 @@ biz.aQute.bnd bnd-maven-plugin - 5.3.0 + 6.0.0 From 1cc16274235f89650349884dd04760bf15a95d96 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Thu, 7 Oct 2021 19:41:44 +0200 Subject: [PATCH 50/90] Fix incorrect feature request template label (#1982) --- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 87ed0f8ee3..176fa7a150 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,7 @@ name: Feature request about: Request a feature. ⚠️ Gson is in maintenance mode; large feature requests might be rejected. title: '' -labels: feature-request +labels: enhancement assignees: '' --- From fe30b85224316cabf19f5dd3223843437c297802 Mon Sep 17 00:00:00 2001 From: Lyubomyr Shaydariv Date: Sat, 9 Oct 2021 03:09:43 +0300 Subject: [PATCH 51/90] Support arbitrary Number implementation for Object and Number deserialization (#1290) * Object and Number type adapters number deserialization can be configured * Change wording of ToNumberStrategy documentation * Use inline links in doc sparingly If the element has already been linked before, don't create a link for every subsequent occurrence. See also (slightly dated) https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#inlinelinks * Link to default to-number policies in ToNumberStrategy doc * Reduce code duplication for deserializing Number * Hide default factory constants of NumberTypeAdapter and ObjectTypeAdapter This encapsulates the logic a little bit better. Additionally refactored factory created by NumberTypeAdapter to only create TypeAdapter once and then have factory reuse that adapter for better performance. Co-authored-by: Marcono1234 --- gson/src/main/java/com/google/gson/Gson.java | 14 +- .../java/com/google/gson/GsonBuilder.java | 28 +++- .../java/com/google/gson/ToNumberPolicy.java | 99 +++++++++++++ .../com/google/gson/ToNumberStrategy.java | 71 ++++++++++ .../gson/internal/bind/NumberTypeAdapter.java | 82 +++++++++++ .../gson/internal/bind/ObjectTypeAdapter.java | 41 ++++-- .../gson/internal/bind/TypeAdapters.java | 23 --- .../test/java/com/google/gson/GsonTest.java | 9 +- .../com/google/gson/ToNumberPolicyTest.java | 115 +++++++++++++++ .../ToNumberPolicyFunctionalTest.java | 134 ++++++++++++++++++ 10 files changed, 575 insertions(+), 41 deletions(-) create mode 100644 gson/src/main/java/com/google/gson/ToNumberPolicy.java create mode 100644 gson/src/main/java/com/google/gson/ToNumberStrategy.java create mode 100644 gson/src/main/java/com/google/gson/internal/bind/NumberTypeAdapter.java create mode 100644 gson/src/test/java/com/google/gson/ToNumberPolicyTest.java create mode 100644 gson/src/test/java/com/google/gson/functional/ToNumberPolicyFunctionalTest.java diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index a7938c84e6..1511bbb178 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -47,6 +47,7 @@ import com.google.gson.internal.bind.JsonTreeReader; import com.google.gson.internal.bind.JsonTreeWriter; import com.google.gson.internal.bind.MapTypeAdapterFactory; +import com.google.gson.internal.bind.NumberTypeAdapter; import com.google.gson.internal.bind.ObjectTypeAdapter; import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; import com.google.gson.internal.bind.TypeAdapters; @@ -146,6 +147,8 @@ public final class Gson { final LongSerializationPolicy longSerializationPolicy; final List builderFactories; final List builderHierarchyFactories; + final ToNumberStrategy objectToNumberStrategy; + final ToNumberStrategy numberToNumberStrategy; /** * Constructs a Gson object with default configuration. The default configuration has the @@ -188,7 +191,7 @@ public Gson() { DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, Collections.emptyList(), Collections.emptyList(), - Collections.emptyList()); + Collections.emptyList(), ToNumberPolicy.DOUBLE, ToNumberPolicy.LAZILY_PARSED_NUMBER); } Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy, @@ -198,7 +201,8 @@ public Gson() { LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle, int timeStyle, List builderFactories, List builderHierarchyFactories, - List factoriesToBeAdded) { + List factoriesToBeAdded, + ToNumberStrategy objectToNumberStrategy, ToNumberStrategy numberToNumberStrategy) { this.excluder = excluder; this.fieldNamingStrategy = fieldNamingStrategy; this.instanceCreators = instanceCreators; @@ -216,12 +220,14 @@ public Gson() { this.timeStyle = timeStyle; this.builderFactories = builderFactories; this.builderHierarchyFactories = builderHierarchyFactories; + this.objectToNumberStrategy = objectToNumberStrategy; + this.numberToNumberStrategy = numberToNumberStrategy; List factories = new ArrayList(); // built-in type adapters that cannot be overridden factories.add(TypeAdapters.JSON_ELEMENT_FACTORY); - factories.add(ObjectTypeAdapter.FACTORY); + factories.add(ObjectTypeAdapter.getFactory(objectToNumberStrategy)); // the excluder must precede all adapters that handle user-defined types factories.add(excluder); @@ -241,7 +247,7 @@ public Gson() { doubleAdapter(serializeSpecialFloatingPointValues))); factories.add(TypeAdapters.newFactory(float.class, Float.class, floatAdapter(serializeSpecialFloatingPointValues))); - factories.add(TypeAdapters.NUMBER_FACTORY); + factories.add(NumberTypeAdapter.getFactory(numberToNumberStrategy)); factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY); factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY); factories.add(TypeAdapters.newFactory(AtomicLong.class, atomicLongAdapter(longAdapter))); diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index b2fd74edec..1874e7de9b 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -95,6 +95,8 @@ public final class GsonBuilder { private boolean prettyPrinting = DEFAULT_PRETTY_PRINT; private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE; private boolean lenient = DEFAULT_LENIENT; + private ToNumberStrategy objectToNumberStrategy = ToNumberPolicy.DOUBLE; + private ToNumberStrategy numberToNumberStrategy = ToNumberPolicy.LAZILY_PARSED_NUMBER; /** * Creates a GsonBuilder instance that can be used to build Gson with various configuration @@ -326,6 +328,30 @@ public GsonBuilder setFieldNamingStrategy(FieldNamingStrategy fieldNamingStrateg return this; } + /** + * Configures Gson to apply a specific number strategy during deserialization of {@link Object}. + * + * @param objectToNumberStrategy the actual object-to-number strategy + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @see ToNumberPolicy#DOUBLE The default object-to-number strategy + */ + public GsonBuilder setObjectToNumberStrategy(ToNumberStrategy objectToNumberStrategy) { + this.objectToNumberStrategy = objectToNumberStrategy; + return this; + } + + /** + * Configures Gson to apply a specific number strategy during deserialization of {@link Number}. + * + * @param numberToNumberStrategy the actual number-to-number strategy + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @see ToNumberPolicy#LAZILY_PARSED_NUMBER The default number-to-number strategy + */ + public GsonBuilder setNumberToNumberStrategy(ToNumberStrategy numberToNumberStrategy) { + this.numberToNumberStrategy = numberToNumberStrategy; + return this; + } + /** * Configures Gson to apply a set of exclusion strategies during both serialization and * deserialization. Each of the {@code strategies} will be applied as a disjunction rule. @@ -600,7 +626,7 @@ public Gson create() { generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient, serializeSpecialFloatingPointValues, longSerializationPolicy, datePattern, dateStyle, timeStyle, - this.factories, this.hierarchyFactories, factories); + this.factories, this.hierarchyFactories, factories, objectToNumberStrategy, numberToNumberStrategy); } private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle, diff --git a/gson/src/main/java/com/google/gson/ToNumberPolicy.java b/gson/src/main/java/com/google/gson/ToNumberPolicy.java new file mode 100644 index 0000000000..1c6f349dc5 --- /dev/null +++ b/gson/src/main/java/com/google/gson/ToNumberPolicy.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * 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 com.google.gson; + +import java.io.IOException; +import java.math.BigDecimal; + +import com.google.gson.internal.LazilyParsedNumber; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.MalformedJsonException; + +/** + * An enumeration that defines two standard number reading strategies and a couple of + * strategies to overcome some historical Gson limitations while deserializing numbers as + * {@link Object} and {@link Number}. + * + * @see ToNumberStrategy + */ +public enum ToNumberPolicy implements ToNumberStrategy { + + /** + * Using this policy will ensure that numbers will be read as {@link Double} values. + * This is the default strategy used during deserialization of numbers as {@link Object}. + */ + DOUBLE { + @Override public Double readNumber(JsonReader in) throws IOException { + return in.nextDouble(); + } + }, + + /** + * Using this policy will ensure that numbers will be read as a lazily parsed number backed + * by a string. This is the default strategy used during deserialization of numbers as + * {@link Number}. + */ + LAZILY_PARSED_NUMBER { + @Override public Number readNumber(JsonReader in) throws IOException { + return new LazilyParsedNumber(in.nextString()); + } + }, + + /** + * Using this policy will ensure that numbers will be read as {@link Long} or {@link Double} + * values depending on how JSON numbers are represented: {@code Long} if the JSON number can + * be parsed as a {@code Long} value, or otherwise {@code Double} if it can be parsed as a + * {@code Double} value. If the parsed double-precision number results in a positive or negative + * infinity ({@link Double#isInfinite()}) or a NaN ({@link Double#isNaN()}) value and the + * {@code JsonReader} is not {@link JsonReader#isLenient() lenient}, a {@link MalformedJsonException} + * is thrown. + */ + LONG_OR_DOUBLE { + @Override public Number readNumber(JsonReader in) throws IOException, JsonParseException { + String value = in.nextString(); + try { + return Long.parseLong(value); + } catch (NumberFormatException longE) { + try { + Double d = Double.valueOf(value); + if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) { + throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + in); + } + return d; + } catch (NumberFormatException doubleE) { + throw new JsonParseException("Cannot parse " + value, doubleE); + } + } + } + }, + + /** + * Using this policy will ensure that numbers will be read as numbers of arbitrary length + * using {@link BigDecimal}. + */ + BIG_DECIMAL { + @Override public BigDecimal readNumber(JsonReader in) throws IOException { + String value = in.nextString(); + try { + return new BigDecimal(value); + } catch (NumberFormatException e) { + throw new JsonParseException("Cannot parse " + value, e); + } + } + } + +} diff --git a/gson/src/main/java/com/google/gson/ToNumberStrategy.java b/gson/src/main/java/com/google/gson/ToNumberStrategy.java new file mode 100644 index 0000000000..db42a4efe6 --- /dev/null +++ b/gson/src/main/java/com/google/gson/ToNumberStrategy.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * 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 com.google.gson; + +import java.io.IOException; + +import com.google.gson.stream.JsonReader; + +/** + * A strategy that is used to control how numbers should be deserialized for {@link Object} and {@link Number} + * when a concrete type of the deserialized number is unknown in advance. By default, Gson uses the following + * deserialization strategies: + * + *