| /* |
| * 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.ArtificialRescue.Rescue; |
| import com.google.gwt.core.client.impl.Impl; |
| 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.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() + "\") : \"" |
| + SerializationUtils.getRpcTypeName(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 "\" " + SerializationUtils.getRpcTypeName(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(SerializationUtils.getRpcTypeName(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 = \""); |
| 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("}),"); |
| } |
| } |
| } |