blob: c9d10620092a7f08ffa82c7e3a525eba261ef707 [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.rebind;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.impl.ArtificialRescue;
import com.google.gwt.core.client.impl.Impl;
import com.google.gwt.core.client.impl.ArtificialRescue.Rescue;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JEnumType;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.javac.TypeOracleMediator;
import com.google.gwt.dev.util.collect.Lists;
import com.google.gwt.rpc.client.impl.CommandToStringWriter;
import com.google.gwt.rpc.client.impl.RpcServiceProxy;
import com.google.gwt.rpc.client.impl.TypeOverrides;
import com.google.gwt.rpc.linker.RpcDataArtifact;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.google.gwt.user.client.rpc.impl.RemoteServiceProxy;
import com.google.gwt.user.linker.rpc.RpcLogArtifact;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.user.rebind.rpc.CustomFieldSerializerValidator;
import com.google.gwt.user.rebind.rpc.ProxyCreator;
import com.google.gwt.user.rebind.rpc.SerializableTypeOracle;
import com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilder;
import com.google.gwt.user.rebind.rpc.SerializationUtils;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Generates async proxy implementations using the RPC system.
*/
public class RpcProxyCreator extends ProxyCreator {
private String typeOverrideName;
public RpcProxyCreator(JClassType type) {
super(type);
}
@Override
protected String computeTypeNameExpression(JType paramType) {
if (paramType.isClass() != null) {
return "GWT.isScript() ? Impl.getNameOf(\"@"
+ paramType.getQualifiedSourceName() + "\") : \""
+ TypeOracleMediator.computeBinaryClassName(paramType) + "\"";
} else {
/*
* Consider the case of service methods that have interface parameters;
* these types don't necessarily exist in the client code, so we want to
* encode these type names in a way that can always be distinguished from
* obfuscated type names.
*/
return "\" " + TypeOracleMediator.computeBinaryClassName(paramType)
+ "\"";
}
}
@Override
protected void generateProxyContructor(SourceWriter srcWriter) {
srcWriter.println("public " + getProxySimpleName() + "() {");
srcWriter.indent();
srcWriter.println("super(GWT.getModuleBaseURL(),");
srcWriter.indent();
srcWriter.println(getRemoteServiceRelativePath() + ",");
srcWriter.println("OVERRIDES);");
srcWriter.outdent();
srcWriter.outdent();
srcWriter.println("}");
}
/**
* Generate any fields required by the proxy.
*/
@Override
protected void generateProxyFields(SourceWriter srcWriter,
SerializableTypeOracle serializableTypeOracle,
String serializationPolicyStrongName, String remoteServiceInterfaceName) {
// Initialize a field with binary name of the remote service interface
srcWriter.println("private static final String REMOTE_SERVICE_INTERFACE_NAME = "
+ "\"" + remoteServiceInterfaceName + "\";");
srcWriter.println("private static final " + TypeOverrides.class.getName()
+ " OVERRIDES = GWT.isScript() ? " + typeOverrideName
+ ".create() : null;");
srcWriter.println();
}
@Override
protected void generateStreamWriterOverride(SourceWriter srcWriter) {
// Intentional no-op. Called if elideTypeNames is on, which is ignored
}
@Override
protected void generateTypeHandlers(TreeLogger logger, GeneratorContext ctx,
SerializableTypeOracle serializationSto,
SerializableTypeOracle deserializationSto)
throws UnableToCompleteException {
String simpleName = serviceIntf.getSimpleSourceName()
+ "_TypeOverridesFactory";
PrintWriter out = ctx.tryCreate(logger, serviceIntf.getPackage().getName(),
simpleName);
if (out == null) {
return;
}
TypeOracle typeOracle = ctx.getTypeOracle();
JClassType objectType = typeOracle.getJavaLangObject();
Set<JType> classLiterals = new LinkedHashSet<JType>();
Map<JType, JMethod> serializerMethods = new LinkedHashMap<JType, JMethod>();
Map<JType, List<String>> fields = new LinkedHashMap<JType, List<String>>();
StringBuilder sb = writeArtificialRescues(typeOracle, serializationSto,
deserializationSto);
ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
serviceIntf.getPackage().getName(), simpleName);
composerFactory.addImport(ArtificialRescue.class.getCanonicalName());
composerFactory.addImport(GWT.class.getCanonicalName());
composerFactory.addImport(Impl.class.getCanonicalName());
composerFactory.addImport(Rescue.class.getCanonicalName());
composerFactory.addImport(TypeOverrides.class.getCanonicalName());
composerFactory.addImport(TypeOverrides.SerializeFunction.class.getCanonicalName());
composerFactory.addAnnotationDeclaration(sb.toString());
SourceWriter sw = composerFactory.createSourceWriter(ctx, out);
sw.println("public static TypeOverrides create() {");
sw.indent();
sw.println("TypeOverrides toReturn = TypeOverrides.create();");
for (JType type : serializationSto.getSerializableTypes()) {
JClassType classType = type.isClass();
if (classType == null) {
continue;
}
/*
* Figure out which fields should be serialized and if there's a CFS. This
* is done by crawling the supertype chain until we hit Object or a type
* with a CFS.
*/
boolean allFieldsAreSerializable = true;
List<String> fieldRefs = new ArrayList<String>();
JMethod serializerMethod = null;
do {
JClassType customSerializer = SerializableTypeOracleBuilder.findCustomFieldSerializer(
typeOracle, classType);
serializerMethod = customSerializer == null ? null
: CustomFieldSerializerValidator.getSerializationMethod(
customSerializer, type.isClass());
if (serializerMethod != null) {
// Don't include any fields in the type
break;
}
JField[] serializableFields = SerializationUtils.getSerializableFields(
typeOracle, classType);
allFieldsAreSerializable &= serializableFields.length == classType.getFields().length;
for (JField field : serializableFields) {
fieldRefs.add("@" + field.getEnclosingType().getQualifiedSourceName()
+ "::" + field.getName());
}
classType = classType.getSuperclass();
} while (classType != objectType);
if (allFieldsAreSerializable && serializerMethod == null) {
// We can just inspect the object at runtime; best for code size
continue;
}
if (serializerMethod != null || !fieldRefs.isEmpty()) {
classLiterals.add(type);
/*
* toReturn.set(class_foo_Bar().getName(), serializer_foo_Bar(),
* fields_foo_Bar());
*/
String mangledTypeName = type.getQualifiedSourceName().replace('.', '_');
sw.println("toReturn.set(class_" + mangledTypeName + "().getName()");
if (serializerMethod == null) {
} else {
serializerMethods.put(type, serializerMethod);
sw.indentln(",serializer_" + mangledTypeName + "()");
}
if (fieldRefs.isEmpty()) {
sw.indentln(");");
} else {
fields.put(type, fieldRefs);
sw.indentln(",fields_" + mangledTypeName + "());");
}
}
}
sw.println("return toReturn;");
sw.outdent();
sw.println("}");
for (JType classLiteral : classLiterals) {
sw.println("public static native Class class_"
+ classLiteral.getQualifiedSourceName().replace('.', '_') + "() /*-{");
sw.indentln("return @" + classLiteral.getQualifiedSourceName()
+ "::class;");
sw.println("}-*/;");
sw.println();
}
for (Map.Entry<JType, JMethod> entry : serializerMethods.entrySet()) {
sw.println("public static native "
+ TypeOverrides.SerializeFunction.class.getSimpleName()
+ " serializer_"
+ entry.getKey().getQualifiedSourceName().replace('.', '_')
+ "() /*-{");
sw.indentln("return " + entry.getValue().getJsniSignature() + ";");
sw.println("}-*/;");
sw.println();
}
for (Map.Entry<JType, List<String>> entry : fields.entrySet()) {
sw.println("public static String[] fields_"
+ entry.getKey().getQualifiedSourceName().replace('.', '_') + "() {");
sw.print("return new String[] {");
for (String fieldRef : entry.getValue()) {
sw.print("Impl.getNameOf(\"" + fieldRef + "\"),");
}
sw.println("};");
sw.println("}");
sw.println();
}
sw.commit(logger);
typeOverrideName = composerFactory.getCreatedClassName();
}
@Override
protected Class<? extends RemoteServiceProxy> getProxySupertype() {
return RpcServiceProxy.class;
}
@Override
protected Class<? extends SerializationStreamWriter> getStreamWriterClass() {
return CommandToStringWriter.class;
}
@Override
protected String writeSerializationPolicyFile(TreeLogger logger,
GeneratorContext ctx, SerializableTypeOracle serializationSto,
SerializableTypeOracle deserializationSto)
throws UnableToCompleteException {
RpcDataArtifact data = new RpcDataArtifact(
serviceIntf.getQualifiedSourceName());
for (JType type : deserializationSto.getSerializableTypes()) {
if (!(type instanceof JClassType)) {
continue;
}
JField[] serializableFields = SerializationUtils.getSerializableFields(
ctx.getTypeOracle(), (JClassType) type);
List<String> names = Lists.create();
for (int i = 0, j = serializableFields.length; i < j; i++) {
names = Lists.add(names, serializableFields[i].getName());
}
data.setFields(TypeOracleMediator.computeBinaryClassName(type), names);
}
ctx.commitArtifact(logger, data);
return RpcLogArtifact.UNSPECIFIED_STRONGNAME;
}
private StringBuilder writeArtificialRescues(TypeOracle typeOracle,
SerializableTypeOracle serializationSto,
SerializableTypeOracle deserializationSto) {
Set<JType> serializableTypes = new LinkedHashSet<JType>();
Collections.addAll(serializableTypes,
serializationSto.getSerializableTypes());
Collections.addAll(serializableTypes,
deserializationSto.getSerializableTypes());
for (JMethod m : serviceIntf.getOverridableMethods()) {
// Pick up any primitive return types, which get sent boxed
JPrimitiveType mustBox = m.getReturnType().isPrimitive();
if (mustBox != null) {
serializableTypes.add(m.getReturnType());
}
}
StringBuilder sb = new StringBuilder("@ArtificialRescue({");
for (JType serializableType : serializableTypes) {
JArrayType serializableArray = serializableType.isArray();
JClassType serializableClass = serializableType.isClass();
JPrimitiveType serializablePrimitive = serializableType.isPrimitive();
if (serializableArray != null) {
sb.append("\n@Rescue(className = \"");
if (serializableArray.getLeafType() instanceof JPrimitiveType) {
sb.append(serializableArray.getLeafType().getJNISignature());
for (int i = 0, j = serializableArray.getRank(); i < j; i++) {
sb.append("[]");
}
} else {
sb.append(serializableArray.getQualifiedSourceName());
}
sb.append("\",\n instantiable = true),");
} else if (serializableClass != null) {
writeSingleRescue(typeOracle, deserializationSto, sb, serializableClass);
} else if (serializablePrimitive != null) {
JClassType boxedClass = typeOracle.findType(serializablePrimitive.getQualifiedBoxedSourceName());
assert boxedClass != null : "No boxed version of "
+ serializablePrimitive.getQualifiedSourceName();
writeSingleRescue(typeOracle, deserializationSto, sb, boxedClass);
}
}
sb.append("})");
return sb;
}
/**
* Writes the rescue of a serializable type and its custom serialization
* logic.
*/
private void writeSingleRescue(TypeOracle typeOracle,
SerializableTypeOracle deserializationOracle, StringBuilder sb,
JClassType serializableClass) {
boolean shouldDeserialize = deserializationOracle.isSerializable(serializableClass);
// Pull the two custom serialization methods
JClassType customSerializer;
JMethod deserialize = null;
JMethod instantiate = null;
// An automatically-serializable subclass of a manually serialized class
boolean hybridSerialization = false;
{
JClassType search = serializableClass;
do {
customSerializer = SerializableTypeOracleBuilder.findCustomFieldSerializer(
typeOracle, search);
if (customSerializer != null) {
instantiate = CustomFieldSerializerValidator.getInstantiationMethod(
customSerializer, search);
deserialize = CustomFieldSerializerValidator.getDeserializationMethod(
customSerializer, search);
hybridSerialization = search != serializableClass;
break;
}
search = search.getSuperclass();
} while (search != null);
}
// The fields that should be preserved from being pruned
JField[] serializableFields;
JEnumType enumType = serializableClass.isEnum();
if (enumType != null) {
serializableFields = enumType.getFields();
} else {
serializableFields = SerializationUtils.getSerializableFields(typeOracle,
serializableClass);
}
/*
* We need to rescue the constructor if there is no instantiate method and
* there is a custom deserialize method.
*/
boolean rescueConstructor = instantiate == null && deserialize != null;
/*
* There may be either no custom serializer or a custom serializer that
* doesn't define the instantiate method.
*/
if (shouldDeserialize || rescueConstructor
|| (customSerializer == null && serializableFields.length > 0)) {
/*
* @Rescue(className="package.Foo$Inner", instantiable=true, fields={..},
* methods={..}),
*/
sb.append("\n@Rescue(className = \"").append(
serializableClass.getQualifiedSourceName()).append("\"");
sb.append(",\n instantiable = ").append(shouldDeserialize);
sb.append(",\n fields = {");
if (customSerializer == null || hybridSerialization) {
for (JField field : serializableFields) {
sb.append("\"").append(field.getName()).append("\",");
}
}
sb.append("},\n methods = {");
if (rescueConstructor) {
sb.append("\"").append(serializableClass.getName().replace('.', '$')).append(
"()\"");
}
sb.append("}),");
}
// Rescue the custom serialization logic if any exists
if (customSerializer != null) {
sb.append("\n@Rescue(className = \"").append(
customSerializer.getQualifiedSourceName()).append("\",\n methods = {");
if (instantiate != null) {
String jsniSignature = instantiate.getJsniSignature();
sb.append("\"").append(
jsniSignature.substring(jsniSignature.lastIndexOf(':') + 1)).append(
"\",");
}
if (deserialize != null) {
String jsniSignature = deserialize.getJsniSignature();
sb.append("\"").append(
jsniSignature.substring(jsniSignature.lastIndexOf(':') + 1)).append(
"\",");
}
sb.append("}),");
}
}
}