All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.user.client.rpc.impl.ClientSerializationStreamReader Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * Copyright 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.gwt.user.client.rpc.impl;

import com.google.gwt.dev.jjs.SourceOrigin;
import com.google.gwt.dev.js.JsParser;
import com.google.gwt.dev.js.ast.JsArrayLiteral;
import com.google.gwt.dev.js.ast.JsBinaryOperation;
import com.google.gwt.dev.js.ast.JsBinaryOperator;
import com.google.gwt.dev.js.ast.JsBooleanLiteral;
import com.google.gwt.dev.js.ast.JsContext;
import com.google.gwt.dev.js.ast.JsExpression;
import com.google.gwt.dev.js.ast.JsInvocation;
import com.google.gwt.dev.js.ast.JsModVisitor;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsNumberLiteral;
import com.google.gwt.dev.js.ast.JsPostfixOperation;
import com.google.gwt.dev.js.ast.JsPrefixOperation;
import com.google.gwt.dev.js.ast.JsRootScope;
import com.google.gwt.dev.js.ast.JsStatement;
import com.google.gwt.dev.js.ast.JsStringLiteral;
import com.google.gwt.dev.js.ast.JsUnaryOperator;
import com.google.gwt.dev.js.ast.JsValueLiteral;
import com.google.gwt.dev.js.ast.JsVisitor;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.Base64Utils;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

/**
 * A version of ClientSerializationStreamReader which is optimized for performance in
 * devmode.
 * 
 * There is a super-src version of this class which is optimized for webmode performance.
 */
public final class ClientSerializationStreamReader extends
    AbstractSerializationStreamReader {

  /**
   * Decodes an RPC payload from a JS string. There is currently no design document that describes
   * the payload, instead you must infer it from reading ServerSerializationStreamWriter.
   * I'll briefly describe the payload here:
   * 

* The server sends down a string of JavaScript which is eval'ed by the webmode version of * ClientSerializationStreamReader into an array. The array contains primitive values, * followed by a nested array of strings, followed by a couple of header primitive values. * For example, *

   * [ 1, 0, 3, -7, 13, [ "string one", "string two", "string three" ], 7, 0 ] 
   * 
* Long primitives are encoded as strings in the outer array, and strings in the string table * are referenced by index values in the outer array. *

* The payload is almost a JSON literal except for some nuances, like unicode and array concats. * We have a specialized devmode version to decode this payload, because the webmode version * requires multiple round-trips between devmode and the JSVM for every single element in the * payload. This can require several seconds to decode a single RPC payload. The RpcDecoder * operates by doing a limited JS parse on the payload within the devmode VM using Rhino, * avoiding the cross-process RPCs. *

*/ private static class RpcDecoder extends JsVisitor { private static final String JS_INFINITY_LITERAL = "Infinity"; private static final String JS_NAN_LITERAL = "NaN"; enum State { EXPECTING_PAYLOAD_BEGIN, EXPECTING_STRING_TABLE, IN_STRING_TABLE, EXPECTING_END } State state = State.EXPECTING_PAYLOAD_BEGIN; List stringTable = new ArrayList(); List values = new ArrayList(); boolean negative; @Override public void endVisit(JsArrayLiteral x, JsContext ctx) { if (state == State.IN_STRING_TABLE) { state = State.EXPECTING_END; } } public List getStringTable() { return stringTable; } public List getValues() { return values; } @Override public boolean visit(JsArrayLiteral x, JsContext ctx) { switch (state) { case EXPECTING_PAYLOAD_BEGIN: state = State.EXPECTING_STRING_TABLE; return true; case EXPECTING_STRING_TABLE: state = State.IN_STRING_TABLE; return true; default: throw new RuntimeException("Unexpected array in RPC payload. The string table has " + "already started."); } } @Override public boolean visit(JsBooleanLiteral x, JsContext ctx) { values.add(x); return true; } @Override public boolean visit(JsNameRef x, JsContext ctx) { String ident = x.getIdent(); if (ident.equals(JS_NAN_LITERAL)) { values.add(new JsNumberLiteral(SourceOrigin.UNKNOWN, Double.NaN)); } else if (ident.equals(JS_INFINITY_LITERAL)) { double val = negative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; negative = false; values.add(new JsNumberLiteral(SourceOrigin.UNKNOWN, val)); } else { throw new RuntimeException("Unexpected identifier: " + ident); } return true; } @Override public boolean visit(JsNumberLiteral x, JsContext ctx) { if (negative) { x = new JsNumberLiteral(x.getSourceInfo(), -x.getValue()); negative = false; } values.add(x); return true; } @Override public boolean visit(JsPrefixOperation x, JsContext ctx) { if (x.getOperator().equals(JsUnaryOperator.NEG)) { negative = !negative; return true; } // Lots of prefix operators, but we only see negatives for literals throw new RuntimeException("Unexpected prefix operator: " + x.toSource()); } @Override public boolean visit(JsPostfixOperation x, JsContext ctx) { throw new RuntimeException("Unexpected postfix operator: " + x.toSource()); } @Override public boolean visit(JsStringLiteral x, JsContext ctx) { if (state == State.IN_STRING_TABLE) { stringTable.add(x.getValue()); } else { values.add(x); } return true; } } private RpcDecoder decoder; private int index; private Serializer serializer; /** * The server breaks up large arrays in the RPC payload into smaller arrays using concat() * expressions. For example, [1, 2, 3, 4, 5, 6, 7] can be broken up into * [1, 2].concat([3, 4], [5, 6], [7,8]) *

* This visitor reverses that transform by reducing all concat invocations into a single array * literal. */ private static class ArrayConcatEvaler extends JsModVisitor { @Override public boolean visit(JsInvocation invoke, JsContext ctx) { JsExpression expr = invoke.getQualifier(); if (!(expr instanceof JsNameRef)) { return super.visit(invoke, ctx); } JsNameRef name = (JsNameRef) expr; if (!name.getIdent().equals("concat")) { return super.visit(invoke, ctx); } JsArrayLiteral headElements = (JsArrayLiteral) name.getQualifier(); for (JsExpression ex : invoke.getArguments()) { JsArrayLiteral arg = (JsArrayLiteral) ex; headElements.getExpressions().addAll(arg.getExpressions()); } ctx.replaceMe(headElements); return true; } } /** * The server splits up string literals into 64KB chunks using '+' operators. For example * ['chunk1chunk2'] is broken up into ['chunk1' + 'chunk2']. *

* This visitor reverses that transform by reducing such strings into a single string literal. */ private static class StringConcatEvaler extends JsModVisitor { @Override public boolean visit(JsBinaryOperation x, JsContext ctx) { if (x.getOperator() != JsBinaryOperator.ADD) { return super.visit(x, ctx); } // Do a first pass to get the total string length to avoid dynamically resizing the buffer. int stringLength = getStringLength(x); if (stringLength >= 0) { StringBuilder builder = new StringBuilder(stringLength); if (expressionToString(x, builder)) { ctx.replaceMe(new JsStringLiteral(x.getSourceInfo(), builder.toString())); } } return true; } /** * Transforms an expression into a string. This will recurse into JsBinaryOperations of type * JsBinaryOperator.ADD, which may have other ADD operations or JsStringLiterals as arguments. * * @param expression the expression to evaluate * @param builder a builder that the string will be appended to * @return true if the expression represents a valid string, or false otherwise */ private boolean expressionToString(JsExpression expression, StringBuilder builder) { if (expression instanceof JsStringLiteral) { builder.append(((JsStringLiteral) expression).getValue()); return true; } if (expression instanceof JsBinaryOperation) { JsBinaryOperation operation = (JsBinaryOperation) expression; if (operation.getOperator() != JsBinaryOperator.ADD) { return false; } return expressionToString(operation.getArg1(), builder) && expressionToString(operation.getArg2(), builder); } return false; } /** * Gets the total string length of the given expression. This will recurse into * JsBinaryOperations of type JsBinaryOperator.ADD, which may have other ADD operations or * JsStringLiterals as arguments. * * @param expression the expression to evaluate * @return the total string length, or -1 if the given expression does not represent a valid * string */ private int getStringLength(JsExpression expression) { if (expression instanceof JsStringLiteral) { return ((JsStringLiteral) expression).getValue().length(); } if (expression instanceof JsBinaryOperation) { JsBinaryOperation operation = (JsBinaryOperation) expression; if (operation.getOperator() != JsBinaryOperator.ADD) { return -1; } int arg1Length = getStringLength(operation.getArg1()); int arg2Length = getStringLength(operation.getArg2()); return (arg1Length >= 0 && arg2Length >= 0) ? (arg1Length + arg2Length) : -1; } return -1; } } public ClientSerializationStreamReader(Serializer serializer) { this.serializer = serializer; } @Override public void prepareToRead(String encoded) throws SerializationException { try { List stmts = JsParser.parse(SourceOrigin.UNKNOWN, JsRootScope.INSTANCE, new StringReader(encoded)); ArrayConcatEvaler arrayConcatEvaler = new ArrayConcatEvaler(); arrayConcatEvaler.acceptList(stmts); StringConcatEvaler stringConcatEvaler = new StringConcatEvaler(); stringConcatEvaler.acceptList(stmts); decoder = new RpcDecoder(); decoder.acceptList(stmts); } catch (Exception e) { throw new SerializationException("Failed to parse RPC payload", e); } index = decoder.getValues().size(); super.prepareToRead(encoded); if (getVersion() < SERIALIZATION_STREAM_MIN_VERSION || getVersion() > SERIALIZATION_STREAM_MAX_VERSION) { throw new IncompatibleRemoteServiceException("Got version " + getVersion() + ", expected version between " + SERIALIZATION_STREAM_MIN_VERSION + " and " + SERIALIZATION_STREAM_MAX_VERSION); } if (!areFlagsValid()) { throw new IncompatibleRemoteServiceException("Got an unknown flag from " + "server: " + getFlags()); } } @Override public boolean readBoolean() { JsValueLiteral literal = decoder.getValues().get(--index); return literal.isBooleanTrue(); } @Override public byte readByte() { JsNumberLiteral literal = (JsNumberLiteral) decoder.getValues().get(--index); return (byte) literal.getValue(); } @Override public char readChar() { JsNumberLiteral literal = (JsNumberLiteral) decoder.getValues().get(--index); return (char) literal.getValue(); } @Override public double readDouble() { JsValueLiteral valueLiteral = decoder.getValues().get(--index); if (valueLiteral instanceof JsNumberLiteral) { JsNumberLiteral literal = (JsNumberLiteral) valueLiteral; return literal.getValue(); } else if (valueLiteral instanceof JsStringLiteral) { JsStringLiteral literal = (JsStringLiteral) valueLiteral; return Double.parseDouble(literal.getValue()); } else { throw new RuntimeException("Can't read double from " + valueLiteral.getKind() + " literal"); } } @Override public float readFloat() { return (float) readDouble(); } @Override public int readInt() { JsNumberLiteral literal = (JsNumberLiteral) decoder.getValues().get(--index); return (int) literal.getValue(); } @Override public long readLong() { return Base64Utils.longFromBase64( ((JsStringLiteral) decoder.getValues().get(--index)).getValue()); } @Override public short readShort() { JsNumberLiteral literal = (JsNumberLiteral) decoder.getValues().get(--index); return (short) literal.getValue(); } @Override public String readString() { return getString(readInt()); } @Override protected Object deserialize(String typeSignature) throws SerializationException { int id = reserveDecodedObjectIndex(); Object instance = serializer.instantiate(this, typeSignature); rememberDecodedObject(id, instance); serializer.deserialize(this, instance, typeSignature); return instance; } @Override protected String getString(int index) { // index is 1-based return index > 0 ? decoder.getStringTable().get(index - 1) : null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy