| /* |
| * 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 |
| }-*/; |
| } |