blob: e565bb6f27a8a129df2f19155ede5dd09800801e [file] [log] [blame]
/*
* Copyright 2009 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.rpc.client.impl;
import com.google.gwt.core.client.GWT;
import com.google.gwt.rpc.client.ast.ArrayValueCommand;
import com.google.gwt.rpc.client.ast.BooleanValueCommand;
import com.google.gwt.rpc.client.ast.ByteValueCommand;
import com.google.gwt.rpc.client.ast.CharValueCommand;
import com.google.gwt.rpc.client.ast.CommandSink;
import com.google.gwt.rpc.client.ast.DoubleValueCommand;
import com.google.gwt.rpc.client.ast.EnumValueCommand;
import com.google.gwt.rpc.client.ast.FloatValueCommand;
import com.google.gwt.rpc.client.ast.InstantiateCommand;
import com.google.gwt.rpc.client.ast.IntValueCommand;
import com.google.gwt.rpc.client.ast.InvokeCustomFieldSerializerCommand;
import com.google.gwt.rpc.client.ast.LongValueCommand;
import com.google.gwt.rpc.client.ast.NullValueCommand;
import com.google.gwt.rpc.client.ast.ReturnCommand;
import com.google.gwt.rpc.client.ast.RpcCommand;
import com.google.gwt.rpc.client.ast.RpcCommandVisitor;
import com.google.gwt.rpc.client.ast.SetCommand;
import com.google.gwt.rpc.client.ast.ShortValueCommand;
import com.google.gwt.rpc.client.ast.StringValueCommand;
import com.google.gwt.rpc.client.ast.ThrowCommand;
import com.google.gwt.rpc.client.ast.ValueCommand;
import com.google.gwt.user.client.rpc.SerializationException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* This implementation of CommandSink encodes RpcCommands in a simple transport
* format that can be interpreted by both the client and the server.
*/
public class SimplePayloadSink extends CommandSink {
private class Visitor extends RpcCommandVisitor {
@Override
public void endVisit(BooleanValueCommand x, Context ctx) {
appendTypedData(BOOLEAN_TYPE, x.getValue() ? "1" : "0");
}
@Override
public void endVisit(ByteValueCommand x, Context ctx) {
appendTypedData(BYTE_TYPE, x.getValue().toString());
}
@Override
public void endVisit(CharValueCommand x, Context ctx) {
appendTypedData(CHAR_TYPE, String.valueOf((int) x.getValue()));
}
@Override
public void endVisit(DoubleValueCommand x, Context ctx) {
appendTypedData(DOUBLE_TYPE, x.getValue().toString());
}
@Override
public void endVisit(EnumValueCommand x, Context ctx) {
// ETypeSeedName~IOrdinal~
if (appendIdentity(x)) {
appendTypedData(ENUM_TYPE, x.getValue().getDeclaringClass().getName());
// use ordinal (and not name), since name might have been obfuscated
appendTypedData(INT_TYPE, String.valueOf(x.getValue().ordinal()));
}
}
@Override
public void endVisit(FloatValueCommand x, Context ctx) {
appendTypedData(FLOAT_TYPE, x.getValue().toString());
}
@Override
public void endVisit(IntValueCommand x, Context ctx) {
appendTypedData(INT_TYPE, x.getValue().toString());
}
@Override
public void endVisit(LongValueCommand x, Context ctx) {
appendTypedData(LONG_TYPE, x.getValue().toString());
}
@Override
public void endVisit(NullValueCommand x, Context ctx) {
appendTypedData(VOID_TYPE, "");
}
@Override
public void endVisit(ShortValueCommand x, Context ctx) {
appendTypedData(SHORT_TYPE, x.getValue().toString());
}
@Override
public void endVisit(StringValueCommand x, Context ctx) {
// "4~abcd
if (appendIdentity(x)) {
String value = x.getValue();
/*
* Emit this a a Pascal-style string, using an explicit length. This
* avoids the need to escape the value.
*/
appendTypedData(STRING_TYPE, String.valueOf(value.length()));
append(value);
}
}
@Override
public boolean visit(ArrayValueCommand x, Context ctx) {
/*
* Encoded as (leafType, dimensions, length, .... )
*
* Object[] foo = new Object[3];
*
* becomes
*
* [ObjectSeedname~1~3~@....~@....~@...~
*
* Object[][] foo = new Object[3][];
*
* becomes
*
* [ObjectSeedName~2~3~...three one-dim arrays...
*/
if (appendIdentity(x)) {
int dims = 1;
Class<?> leaf = x.getComponentType();
while (leaf.getComponentType() != null) {
dims++;
leaf = leaf.getComponentType();
}
appendTypedData(ARRAY_TYPE, leaf.getName());
accept(new IntValueCommand(dims));
accept(new IntValueCommand(x.getComponentValues().size()));
return true;
} else {
return false;
}
}
@Override
public boolean visit(InstantiateCommand x, Context ctx) {
// @TypeSeedName~3~... N-many setters ...
if (appendIdentity(x)) {
appendTypedData(OBJECT_TYPE, x.getTargetClass().getName());
accept(new IntValueCommand(x.getSetters().size()));
return true;
} else {
return false;
}
}
@Override
public boolean visit(InvokeCustomFieldSerializerCommand x, Context ctx) {
// !TypeSeedName~Number of objects written by CFS~...CFS objects...~
// Number of extra fields~...N-many setters...
if (appendIdentity(x)) {
appendTypedData(INVOKE_TYPE, x.getTargetClass().getName());
accept(new IntValueCommand(x.getValues().size()));
accept(x.getValues());
accept(new IntValueCommand(x.getSetters().size()));
accept(x.getSetters());
return false;
} else {
return false;
}
}
@Override
public boolean visit(ReturnCommand x, Context ctx) {
// R4~...values...
appendTypedData(RETURN_TYPE, String.valueOf(x.getValues().size()));
return true;
}
@Override
public boolean visit(SetCommand x, Context ctx) {
/*
* In hosted-mode, the field's declaring class is written to the stream to
* handle field shadowing. In web mode, this isn't necessary because all
* field names are allocated in the same "object" scope.
*
* DeclaringClassName~FieldName~...value...
*/
if (!GWT.isScript()) {
accept(new StringValueCommand(x.getFieldDeclClass().getName()));
}
accept(new StringValueCommand(x.getField()));
return true;
}
@Override
public boolean visit(ThrowCommand x, Context ctx) {
// T...value...
appendTypedData(THROW_TYPE, "");
return true;
}
private void append(String x) {
try {
buffer.append(EscapeUtil.escape(x)).append(RPC_SEPARATOR_CHAR);
} catch (IOException e) {
halt(e);
}
}
private boolean appendIdentity(ValueCommand x) {
Integer backRef = backRefs.get(x);
if (backRef != null) {
if (PRETTY) {
try {
buffer.append(NL_CHAR);
} catch (IOException e) {
halt(e);
}
}
append(BACKREF_TYPE + String.valueOf(backRef));
return false;
} else {
backRefs.put(x, backRefs.size());
return true;
}
}
private void appendTypedData(char type, String value) {
try {
if (PRETTY) {
buffer.append(NL_CHAR);
}
buffer.append(type).append(value).append(RPC_SEPARATOR_CHAR);
} catch (IOException e) {
halt(e);
}
}
}
/**
* Used for diagnostics.
*/
static final boolean PRETTY = false;
public static final char ARRAY_TYPE = '[';
public static final char BACKREF_TYPE = '@';
public static final char BOOLEAN_TYPE = 'Z';
public static final char BYTE_TYPE = 'B';
public static final char CHAR_TYPE = 'C';
public static final char DOUBLE_TYPE = 'D';
public static final char ENUM_TYPE = 'E';
public static final char FLOAT_TYPE = 'F';
public static final char INT_TYPE = 'I';
public static final char INVOKE_TYPE = '!';
public static final char LONG_TYPE = 'J';
public static final char NL_CHAR = '\n';
public static final char OBJECT_TYPE = 'L';
public static final char RETURN_TYPE = 'R';
public static final char RPC_SEPARATOR_CHAR = '~';
public static final char SHORT_TYPE = 'S';
public static final char STRING_TYPE = '"';
public static final char THROW_TYPE = 'T';
public static final char VOID_TYPE = 'V';
private final Map<ValueCommand, Integer> backRefs = new HashMap<ValueCommand, Integer>();
private final Appendable buffer;
public SimplePayloadSink(Appendable buffer) {
this.buffer = buffer;
}
@Override
public void accept(RpcCommand command) throws SerializationException {
(new Visitor()).accept(command);
}
@Override
public void finish() throws SerializationException {
}
}