Skip to content

Commit a1d2ebc

Browse files
authored
Fix #1702: Gson.toJson creates CharSequence which does not implement toString (#1703)
* Gson.toJson creates CharSequence which does not implement toString * Improve Streams.AppendableWriter.CurrentWrite test * Make setChars package-private
1 parent 4552db2 commit a1d2ebc

File tree

2 files changed

+71
-12
lines changed

2 files changed

+71
-12
lines changed

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ private static final class AppendableWriter extends Writer {
8989
}
9090

9191
@Override public void write(char[] chars, int offset, int length) throws IOException {
92-
currentWrite.chars = chars;
92+
currentWrite.setChars(chars);
9393
appendable.append(currentWrite, offset, offset + length);
9494
}
9595

@@ -122,8 +122,15 @@ private static final class AppendableWriter extends Writer {
122122
/**
123123
* A mutable char sequence pointing at a single char[].
124124
*/
125-
static class CurrentWrite implements CharSequence {
126-
char[] chars;
125+
private static class CurrentWrite implements CharSequence {
126+
private char[] chars;
127+
private String cachedString;
128+
129+
void setChars(char[] chars) {
130+
this.chars = chars;
131+
this.cachedString = null;
132+
}
133+
127134
@Override public int length() {
128135
return chars.length;
129136
}
@@ -133,7 +140,14 @@ static class CurrentWrite implements CharSequence {
133140
@Override public CharSequence subSequence(int start, int end) {
134141
return new String(chars, start, end - start);
135142
}
143+
144+
// Must return string representation to satisfy toString() contract
145+
@Override public String toString() {
146+
if (cachedString == null) {
147+
cachedString = new String(chars);
148+
}
149+
return cachedString;
150+
}
136151
}
137152
}
138-
139153
}

gson/src/test/java/com/google/gson/functional/ReadersWritersTest.java

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,17 @@
2020
import com.google.gson.JsonStreamParser;
2121
import com.google.gson.JsonSyntaxException;
2222
import com.google.gson.common.TestTypes.BagOfPrimitives;
23-
2423
import com.google.gson.reflect.TypeToken;
25-
import java.util.Map;
26-
import junit.framework.TestCase;
27-
2824
import java.io.CharArrayReader;
2925
import java.io.CharArrayWriter;
3026
import java.io.IOException;
3127
import java.io.Reader;
3228
import java.io.StringReader;
3329
import java.io.StringWriter;
3430
import java.io.Writer;
31+
import java.util.Arrays;
32+
import java.util.Map;
33+
import junit.framework.TestCase;
3534

3635
/**
3736
* Functional tests for the support of {@link Reader}s and {@link Writer}s.
@@ -89,8 +88,8 @@ public void testTopLevelNullObjectDeserializationWithReaderAndSerializeNulls() {
8988
}
9089

9190
public void testReadWriteTwoStrings() throws IOException {
92-
Gson gson= new Gson();
93-
CharArrayWriter writer= new CharArrayWriter();
91+
Gson gson = new Gson();
92+
CharArrayWriter writer = new CharArrayWriter();
9493
writer.write(gson.toJson("one").toCharArray());
9594
writer.write(gson.toJson("two").toCharArray());
9695
CharArrayReader reader = new CharArrayReader(writer.toCharArray());
@@ -102,8 +101,8 @@ public void testReadWriteTwoStrings() throws IOException {
102101
}
103102

104103
public void testReadWriteTwoObjects() throws IOException {
105-
Gson gson= new Gson();
106-
CharArrayWriter writer= new CharArrayWriter();
104+
Gson gson = new Gson();
105+
CharArrayWriter writer = new CharArrayWriter();
107106
BagOfPrimitives expectedOne = new BagOfPrimitives(1, 1, true, "one");
108107
writer.write(gson.toJson(expectedOne).toCharArray());
109108
BagOfPrimitives expectedTwo = new BagOfPrimitives(2, 2, false, "two");
@@ -132,4 +131,50 @@ public void testTypeMismatchThrowsJsonSyntaxExceptionForReaders() {
132131
} catch (JsonSyntaxException expected) {
133132
}
134133
}
134+
135+
/**
136+
* Verifies that passing an {@link Appendable} which is not an instance of {@link Writer}
137+
* to {@code Gson.toJson} works correctly.
138+
*/
139+
public void testToJsonAppendable() {
140+
class CustomAppendable implements Appendable {
141+
final StringBuilder stringBuilder = new StringBuilder();
142+
int toStringCallCount = 0;
143+
144+
@Override
145+
public Appendable append(char c) throws IOException {
146+
stringBuilder.append(c);
147+
return this;
148+
}
149+
150+
@Override
151+
public Appendable append(CharSequence csq) throws IOException {
152+
if (csq == null) {
153+
csq = "null"; // Requirement by Writer.append
154+
}
155+
append(csq, 0, csq.length());
156+
return this;
157+
}
158+
159+
@Override
160+
public Appendable append(CharSequence csq, int start, int end) throws IOException {
161+
if (csq == null) {
162+
csq = "null"; // Requirement by Writer.append
163+
}
164+
165+
// According to doc, toString() must return string representation
166+
String s = csq.toString();
167+
toStringCallCount++;
168+
stringBuilder.append(s, start, end);
169+
return this;
170+
}
171+
}
172+
173+
CustomAppendable appendable = new CustomAppendable();
174+
gson.toJson(Arrays.asList("test", 123, true), appendable);
175+
// Make sure CharSequence.toString() was called at least two times to verify that
176+
// CurrentWrite.cachedString is properly overwritten when char array changes
177+
assertTrue(appendable.toStringCallCount >= 2);
178+
assertEquals("[\"test\",123,true]", appendable.stringBuilder.toString());
179+
}
135180
}

0 commit comments

Comments
 (0)