| /* |
| * 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 Development Mode, the field's declaring class is written to the |
| * stream to handle field shadowing. In Production 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 { |
| } |
| } |