blob: 4711981498d18269e5709b253dbbafcc65b7c521 [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.UnsafeNativeLong;
import com.google.gwt.rpc.client.ast.ArrayValueCommand;
import com.google.gwt.rpc.client.ast.BooleanValueCommand;
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.HasSetters;
import com.google.gwt.rpc.client.ast.IdentityValueCommand;
import com.google.gwt.rpc.client.ast.InstantiateCommand;
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.StringValueCommand;
import com.google.gwt.rpc.client.ast.ValueCommand;
import com.google.gwt.rpc.client.impl.TypeOverrides.SerializeFunction;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.google.gwt.user.client.rpc.SerializationException;
import java.io.Serializable;
import java.util.IdentityHashMap;
import java.util.Map;
/**
* Provides a facade around serialization logic in client code.
*/
public class CommandClientSerializationStreamWriter extends
CommandSerializationStreamWriterBase {
private static Object anObject = new Object[] {};
static {
// Don't need to explicitly filter $H
anObject.hashCode();
}
private final Map<Object, IdentityValueCommand> identityMap;
private final TypeOverrides serializer;
public CommandClientSerializationStreamWriter(TypeOverrides serializer,
CommandSink sink) {
this(serializer, sink, new IdentityHashMap<Object, IdentityValueCommand>());
}
private CommandClientSerializationStreamWriter(TypeOverrides serializer,
CommandSink sink, Map<Object, IdentityValueCommand> identityMap) {
super(sink);
this.serializer = serializer;
this.identityMap = identityMap;
}
/**
* Type is passed in to handle primitive types.
*/
@Override
protected ValueCommand makeValue(Class<?> type, Object value)
throws SerializationException {
SerializeFunction customSerializer;
ValueCommand toReturn;
if (value == null) {
toReturn = NullValueCommand.INSTANCE;
} else if (type.isPrimitive()) {
if (type == boolean.class) {
toReturn = new BooleanValueCommand((Boolean) value);
} else if (type == void.class) {
toReturn = NullValueCommand.INSTANCE;
} else if (type == long.class) {
toReturn = new LongValueCommand((Long) value);
} else if (type == char.class) {
toReturn = new CharValueCommand((Character) value);
} else {
assert value instanceof Number : "Expecting Number; had "
+ value.getClass().getName();
toReturn = new DoubleValueCommand(((Number) value).doubleValue());
}
} else if ((toReturn = identityMap.get(value)) != null) {
// Fall through
} else if (type == String.class) {
toReturn = new StringValueCommand((String) value);
} else if (type.isArray()) {
ArrayValueCommand array = new ArrayValueCommand(type.getComponentType());
identityMap.put(value, array);
extractData(array, value);
toReturn = array;
} else if (value instanceof Enum<?>) {
EnumValueCommand e = new EnumValueCommand();
e.setValue((Enum<?>) value);
toReturn = e;
} else if ((customSerializer = serializer.getOverride(type.getName())) != null) {
toReturn = invokeCustomSerializer(customSerializer, type, value);
} else {
toReturn = makeObject(type, value);
}
return toReturn;
}
private native void extractData(ArrayValueCommand x, Object obj) /*-{
for (var i = 0, j = obj.length; i < j; i++) {
var value = this.@com.google.gwt.rpc.client.impl.CommandClientSerializationStreamWriter::makeValue(Ljava/lang/Object;)(obj[i]);
x.@com.google.gwt.rpc.client.ast.ArrayValueCommand::add(Lcom/google/gwt/rpc/client/ast/ValueCommand;)(value);
}
}-*/;
private native void extractData(HasSetters x, Object obj) /*-{
for (var key in obj) {
// Ignore common properties
if (key in @com.google.gwt.rpc.client.impl.CommandClientSerializationStreamWriter::anObject) {
continue;
}
this.@com.google.gwt.rpc.client.impl.CommandClientSerializationStreamWriter::extractField(Lcom/google/gwt/rpc/client/ast/HasSetters;Ljava/lang/Object;Ljava/lang/String;)(x,obj,key);
}
}-*/;
private native void extractField(HasSetters x, Object obj, String key) /*-{
var command = this.@com.google.gwt.rpc.client.impl.CommandClientSerializationStreamWriter::makeValue(Ljava/lang/Object;)(obj[key]);
// makeValue may return undefined
command && x.@com.google.gwt.rpc.client.ast.HasSetters::set(Ljava/lang/Class;Ljava/lang/String;Lcom/google/gwt/rpc/client/ast/ValueCommand;)(null, key, command);
}-*/;
private ValueCommand invokeCustomSerializer(
SerializeFunction serializeFunction, Class<?> type, Object value) {
InvokeCustomFieldSerializerCommand command = new InvokeCustomFieldSerializerCommand(
type, null, null);
identityMap.put(value, command);
/*
* Pass the current identityMap into the new writer to allow circular
* references through the graph emitted by the CFS.
*/
CommandClientSerializationStreamWriter subWriter = new CommandClientSerializationStreamWriter(
serializer, new HasValuesCommandSink(command), identityMap);
serializeFunction.serialize(subWriter, value);
if (serializer.hasExtraFields(type.getName())) {
for (String extraField : serializer.getExtraFields(type.getName())) {
if (extraField != null) {
// Sometimes fields might be pruned
extractField(command, value, extraField);
}
}
}
return command;
}
private ValueCommand makeObject(Class<?> clazz, Object value)
throws SerializationException {
if (!(value instanceof Serializable || value instanceof IsSerializable)) {
throw new SerializationException(clazz.getName()
+ " is not a Serializable type");
}
InstantiateCommand x = new InstantiateCommand(clazz);
identityMap.put(value, x);
if (serializer.hasExtraFields(clazz.getName())) {
// Objects with transient fields or non-trivial semantics
for (String fieldName : serializer.getExtraFields(clazz.getName())) {
extractField(x, value, fieldName);
}
} else {
// Just a for-in loop
extractData(x, value);
}
return x;
}
@SuppressWarnings("unused")
@UnsafeNativeLong
private native ValueCommand makeValue(Object value) /*-{
var type;
if (value) {
// Maybe turn objects into primitives
value.valueOf && (value = value.valueOf());
// See if the value is our web-mode representation of a long
if (value.hasOwnProperty('l') && value.hasOwnProperty('m') && value.hasOwnProperty('h')) {
type = 'long';
}
}
type || (type = typeof value);
switch (type) {
case 'boolean':
return @com.google.gwt.rpc.client.ast.BooleanValueCommand::new(Z)(value);
case 'number':
return @com.google.gwt.rpc.client.ast.DoubleValueCommand::new(D)(value);
case 'string':
return @com.google.gwt.rpc.client.ast.StringValueCommand::new(Ljava/lang/String;)(value);
case 'long':
return @com.google.gwt.rpc.client.ast.LongValueCommand::new(J)(value);
case 'function':
// Not serializable
break;
case 'object':
// typeof null == 'object'
if (!value) {
return @com.google.gwt.rpc.client.ast.NullValueCommand::INSTANCE;
}
if (!value.@java.lang.Object::typeMarker) {
// Not a Java object
break;
}
return this.@com.google.gwt.rpc.client.impl.CommandClientSerializationStreamWriter::makeValue(Ljava/lang/Class;Ljava/lang/Object;)(value.@java.lang.Object::getClass()(), value);
case 'undefined':
// typeof undefined == 'undefined', but we treat it as null
return @com.google.gwt.rpc.client.ast.NullValueCommand::INSTANCE;
default:
throw @java.lang.RuntimeException::new(Ljava/lang/String;)('Unknown type ' + type);
}
// Intentionally return undefined
}-*/;
}