diff --git a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java index ba275dc5ed0..3aa55615a7f 100644 --- a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java +++ b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java @@ -47,7 +47,7 @@ public abstract class AbstractSerializationStream { /** * The current RPC protocol version. */ - public static final int SERIALIZATION_STREAM_VERSION = 7; + public static final int SERIALIZATION_STREAM_VERSION = 8; /** * The oldest supported RPC protocol version. diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java index 5e29935e368..c8ec5568dc5 100644 --- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java +++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java @@ -42,78 +42,6 @@ public final class ServerSerializationStreamWriter extends AbstractSerializationStreamWriter { - /** - * Builds a string that evaluates into an array containing the given elements. - * This class exists to work around a bug in IE6/7 that limits the size of - * array literals. - */ - public static class LengthConstrainedArray { - public static final int MAXIMUM_ARRAY_LENGTH = 1 << 15; - private static final String POSTLUDE = "])"; - private static final String PRELUDE = "].concat(["; - - private final StringBuffer buffer; - private int count = 0; - private boolean needsComma = false; - private int total = 0; - private boolean javascript = false; - - public LengthConstrainedArray() { - buffer = new StringBuffer(); - } - - public LengthConstrainedArray(int capacityGuess) { - buffer = new StringBuffer(capacityGuess); - } - - public void addToken(CharSequence token) { - total++; - if (count++ == MAXIMUM_ARRAY_LENGTH) { - if (total == MAXIMUM_ARRAY_LENGTH + 1) { - buffer.append(PRELUDE); - javascript = true; - } else { - buffer.append("],["); - } - count = 0; - needsComma = false; - } - - if (needsComma) { - buffer.append(","); - } else { - needsComma = true; - } - - buffer.append(token); - } - - public void addEscapedToken(String token) { - addToken(escapeString(token, true, this)); - } - - public void addToken(int i) { - addToken(String.valueOf(i)); - } - - public boolean isJavaScript() { - return javascript; - } - - public void setJavaScript(boolean javascript) { - this.javascript = javascript; - } - - @Override - public String toString() { - if (total > MAXIMUM_ARRAY_LENGTH) { - return "[" + buffer.toString() + POSTLUDE; - } else { - return "[" + buffer.toString() + "]"; - } - } - } - /** * Enumeration used to provided typed instance writers. */ @@ -337,14 +265,6 @@ abstract void write(ServerSerializationStreamWriter stream, Object instance) private static final char NON_BREAKING_HYPHEN = '\u2011'; - /** - * Maximum length of a string node in RPC responses, not including surrounding - * quote characters (2 ^ 16 - 1) = 65535. - * This exists to work around a Rhino parser bug in the hosted mode client - * that limits string node lengths to 64KB. - */ - private static final int MAX_STRING_NODE_LENGTH = 0xFFFF; - static { /* * NOTE: The JS VM in IE6 & IE7 do not interpret \v correctly. They convert @@ -386,32 +306,11 @@ abstract void write(ServerSerializationStreamWriter stream, Object instance) * This method takes a string and outputs a JavaScript string literal. The * data is surrounded with quotes, and any contained characters that need to * be escaped are mapped onto their escape sequence. - * - * Assumptions: We are targeting a version of JavaScript that that is later - * than 1.3 that supports unicode strings. - */ - public static String escapeString(String toEscape) { - return escapeString(toEscape, false, null); - } - - /** - * This method takes a string and outputs a JavaScript string literal. The - * data is surrounded with quotes, and any contained characters that need to - * be escaped are mapped onto their escape sequence. - * - * This splits strings into 64KB chunks to workaround an issue with the hosted mode client where - * the Rhino parser can't handle string nodes larger than 64KB, e.g. {@code "longstring"} is - * converted to {@code "long" + "string"}. * * Assumptions: We are targeting a version of JavaScript that that is later * than 1.3 that supports unicode strings. */ - public static String escapeStringSplitNodes(String toEscape) { - return escapeString(toEscape, true, null); - } - - private static String escapeString(String toEscape, boolean splitNodes, - LengthConstrainedArray array) { + public static String escapeString(String toEscape) { // Since escaped characters will increase the output size, allocate extra room to start. int length = toEscape.length(); int capacityIncrement = Math.max(length, 16); @@ -422,29 +321,11 @@ private static String escapeString(String toEscape, boolean splitNodes, int i = 0; while (i < length) { - // Add one segment at a time, up to maxNodeLength characters. Note this always leave room - // for at least 6 characters at the end (maximum unicode escaped character size). - int maxSegmentVectorSize = splitNodes - ? (charVector.getSize() + MAX_STRING_NODE_LENGTH - 5) - : Integer.MAX_VALUE; - - while (i < length && charVector.getSize() < maxSegmentVectorSize) { - char c = toEscape.charAt(i++); - if (needsUnicodeEscape(c)) { - unicodeEscape(c, charVector); - } else { - charVector.add(c); - } - } - - // If there's another segment left, insert a '+' operator. - if (splitNodes && i < length) { - charVector.add(JS_QUOTE_CHAR); - charVector.add('+'); - charVector.add(JS_QUOTE_CHAR); - if (array != null) { - array.setJavaScript(true); - } + char c = toEscape.charAt(i++); + if (needsUnicodeEscape(c)) { + unicodeEscape(c, charVector); + } else { + charVector.add(c); } } @@ -502,7 +383,7 @@ private static Class getClassForSerialization(Object instance) { *
  • Total Characters Escaped: 2082
  • * * - * + * * @param ch character to check * @return true if the character requires the \\uXXXX unicode * character escape @@ -557,7 +438,7 @@ private static boolean needsUnicodeEscape(char ch) { * Writes a safe escape sequence for a character. Some characters have a short * form, such as \n for U+000D, while others are represented as \\xNN or * \\uNNNN. - * + * * @param ch character to unicode escape * @param charVector char vector to receive the unicode escaped representation */ @@ -610,7 +491,7 @@ public void serializeValue(Object value, Class type) /** * Build an array of JavaScript string literals that can be decoded by the * client via the eval function. - * + * * NOTE: We build the array in reverse so the client can simply use the pop * function to remove the next item from the list. */ @@ -620,14 +501,14 @@ public String toString() { // We take a guess at how big to make to buffer to avoid numerous resizes. // int capacityGuess = 2 * tokenListCharCount + 2 * tokenList.size(); - LengthConstrainedArray stream = new LengthConstrainedArray(capacityGuess); - writePayload(stream); - writeStringTable(stream); - writeHeader(stream); + StringBuilder buffer = new StringBuilder(capacityGuess); + writePayload(buffer); + writeStringTable(buffer); + writeHeader(buffer); - return stream.toString(); + return "[" + buffer.toString() + "]"; } - + @Override public void writeLong(long value) { if (getVersion() == SERIALIZATION_STREAM_MIN_VERSION) { @@ -702,7 +583,7 @@ protected void serialize(Object instance, String typeSignature) * Serialize an instance that is an array. Will default to serializing the * instance as an Object vector if the instance is not a vector of primitives, * Strings or Object. - * + * * @param instanceClass * @param instance * @throws SerializationException @@ -735,14 +616,14 @@ private void serializeClass(Object instance, Class instanceClass) List serverFields = new ArrayList(); for (Field declField : serializableFields) { assert (declField != null); - + // Identify server-only fields if (!clientFieldNames.contains(declField.getName())) { serverFields.add(declField); continue; } } - + // Serialize the server-only fields into a byte array and encode as a String try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -765,7 +646,7 @@ private void serializeClass(Object instance, Class instanceClass) throw new SerializationException(e); } } - + // Write the client-visible field data for (Field declField : serializableFields) { if ((clientFieldNames != null) && !clientFieldNames.contains(declField.getName())) { @@ -861,29 +742,39 @@ private void serializeWithCustomSerializer(Class customSerializer, * Notice that the field are written in reverse order that the client can just * pop items out of the stream. */ - private void writeHeader(LengthConstrainedArray stream) { - stream.addToken(getFlags()); - if (stream.isJavaScript() && getVersion() >= SERIALIZATION_STREAM_JSON_VERSION) { - // Ensure we are not using the JSON supported version if stream is Javascript instead of JSON - stream.addToken(SERIALIZATION_STREAM_JSON_VERSION - 1); - } else { - stream.addToken(getVersion()); - } + private void writeHeader(StringBuilder buffer) { + addToken(buffer, getFlags()); + addToken(buffer, getVersion()); } - private void writePayload(LengthConstrainedArray stream) { + private void writePayload(StringBuilder buffer) { ListIterator tokenIterator = tokenList.listIterator(tokenList.size()); while (tokenIterator.hasPrevious()) { - stream.addToken(tokenIterator.previous()); + addToken(buffer, tokenIterator.previous()); } } - private void writeStringTable(LengthConstrainedArray stream) { - LengthConstrainedArray tableStream = new LengthConstrainedArray(); + private void writeStringTable(StringBuilder buffer) { + StringBuilder tableBuffer = new StringBuilder(); for (String s : getStringTable()) { - tableStream.addEscapedToken(s); + addEscapedToken(tableBuffer, s); } - stream.addToken(tableStream.toString()); - stream.setJavaScript(stream.isJavaScript() || tableStream.isJavaScript()); + addToken(buffer, "[" + tableBuffer + "]"); + } + + public void addToken(StringBuilder buffer, CharSequence token) { + if (buffer.length() > 0) { + buffer.append(","); + } + + buffer.append(token); + } + + public void addEscapedToken(StringBuilder buffer, String token) { + addToken(buffer, escapeString(token)); + } + + public void addToken(StringBuilder buffer, int i) { + addToken(buffer, String.valueOf(i)); } } diff --git a/user/test/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriterTest.java b/user/test/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriterTest.java index 1655966341d..cfbbc5f9818 100644 --- a/user/test/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriterTest.java +++ b/user/test/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriterTest.java @@ -39,12 +39,12 @@ public void testEscapeString() { } public void testEscapeStringSplitNodes() { - String escaped = ServerSerializationStreamWriter.escapeStringSplitNodes("test"); + String escaped = ServerSerializationStreamWriter.escapeString("test"); assertEquals("\"test\"", escaped); } public void testEscapeStringSplitNodes_unicodeEscape() { - String escaped = ServerSerializationStreamWriter.escapeStringSplitNodes( + String escaped = ServerSerializationStreamWriter.escapeString( "测试" // Unicode characters + "\"" // JS quote char + "\\" // JS escape char @@ -75,11 +75,11 @@ public void testEscapeStringSplitNodes_over64KB() { secondNodeBuilder.append('2'); } - String escaped = ServerSerializationStreamWriter.escapeStringSplitNodes( + String escaped = ServerSerializationStreamWriter.escapeString( firstNodeBuilder.toString() + secondNodeBuilder.toString()); assertEquals( - "\"" + firstNodeBuilder.toString() + "\"+\"" + secondNodeBuilder.toString() + "\"", + "\"" + firstNodeBuilder.toString() + secondNodeBuilder.toString() + "\"", escaped); } @@ -104,12 +104,11 @@ public void testEscapeStringSplitNodes_over64KBEscaped() { } String secondNode = secondNodeBuilder.toString(); - String escaped = ServerSerializationStreamWriter.escapeStringSplitNodes( + String escaped = ServerSerializationStreamWriter.escapeString( firstNodeBuilder.toString() + secondNode); assertEquals( "\"" + firstNodeNoUnicode + "\\u2011" // first node (including escaped unicode character) - + "\"+\"" + secondNode + "\"", // second node escaped); } @@ -122,28 +121,4 @@ public void testWritingRpcVersion8Message() { assertEquals("[\"NaN\",\"Infinity\",\"-Infinity\",[],0,8]", writer.toString()); } - public void testVersion8Fallbacks() { - StringBuilder longString = new StringBuilder(66000); - for (int i = 0; i < 660000; i++) { - longString.append("a"); - } - - // Fallbacks to 7 if string gets concatenated - ServerSerializationStreamWriter writer = new ServerSerializationStreamWriter(null, 8); - writer.writeString(longString.toString()); - String encoded = writer.toString(); - assertEquals("7", encoded.substring(encoded.lastIndexOf(",") + 1, encoded.lastIndexOf("]"))); - - // Fallbacks to 7 if array size reached maximum - int maxArrayLength = - ServerSerializationStreamWriter.LengthConstrainedArray.MAXIMUM_ARRAY_LENGTH + 100; - writer = new ServerSerializationStreamWriter(null, 8); - for (int i = 0; i < maxArrayLength; i++) { - writer.writeInt(i); - } - - encoded = writer.toString(); - assertEquals("7", encoded.substring(encoded.lastIndexOf(",") + 1, encoded.lastIndexOf("]"))); - } - }