001    /*
002     * Copyright 2006 Google Inc.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package com.google.gwt.user.rebind.rpc;
017    
018    import com.google.gwt.core.ext.GeneratorContext;
019    import com.google.gwt.core.ext.TreeLogger;
020    import com.google.gwt.core.ext.typeinfo.JArrayType;
021    import com.google.gwt.core.ext.typeinfo.JClassType;
022    import com.google.gwt.core.ext.typeinfo.JField;
023    import com.google.gwt.core.ext.typeinfo.JType;
024    import com.google.gwt.core.ext.typeinfo.TypeOracle;
025    import com.google.gwt.user.client.rpc.SerializationStreamReader;
026    import com.google.gwt.user.client.rpc.SerializationStreamWriter;
027    import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
028    import com.google.gwt.user.rebind.SourceWriter;
029    
030    import java.io.IOException;
031    import java.io.PrintWriter;
032    
033    /**
034     * <p>
035     * This class creates field serializers for a particular class. If the class has
036     * a custom serializer then that class is used rather than creating one.
037     * 
038     * <p>
039     * Generated field serializers are emitted into the same package as the class
040     * that they serialize.
041     * 
042     * <p>
043     * Fields are considered serializable if:
044     * <ul>
045     * <li>Field is not static
046     * <li>Field is not transient
047     * </ul>
048     * 
049     * TODO(mmendez): Need to make the generated field serializers final
050     * TODO(mmendez): Would be nice to be able to have imports, rather than using
051     * fully qualified type names everywhere
052     */
053    public class FieldSerializerCreator {
054      private JType fSerializableClass;
055      private JField[] fSerializableFields;
056      private SerializableTypeOracle fSerializationOracle;
057      private SourceWriter fSourceWriter;
058      private JClassType fObjectType;
059      
060      public FieldSerializerCreator(SerializableTypeOracle serializationOracle,
061          JType requestedClass) {
062        setSerializationOracle(serializationOracle);
063        fSerializableClass = requestedClass;
064      }
065    
066      public String realize(TreeLogger logger, GeneratorContext ctx)
067          throws IOException {
068        assert (ctx != null);
069        assert (fSerializationOracle.isSerializable(getSerializableClass()));
070    
071        logger = logger.branch(TreeLogger.SPAM, "Generating a field serializer for type '"
072          + fSerializableClass.getQualifiedSourceName() + "'", null);
073        String fieldSerializerName = fSerializationOracle
074          .getFieldSerializerName(getSerializableClass());
075    
076        fSourceWriter = getSourceWriter(logger, ctx);
077        if (fSourceWriter == null) {
078          return fieldSerializerName;
079        }
080        assert fSourceWriter != null;
081    
082        writeFieldAccessors();
083    
084        writeSerializeMethod(ctx);
085    
086        writeDeserializeMethod(ctx);
087    
088        fSourceWriter.commit(logger);
089    
090        return fieldSerializerName;
091      }
092    
093      private JType getSerializableClass() {
094        return fSerializableClass;
095      }
096    
097      private String getSimpleSourceName(JType type) {
098        String sourceName = fSerializationOracle.getFieldSerializerName(type);
099    
100        int qualifiedSourceNameStart = sourceName.lastIndexOf('.');
101        if (qualifiedSourceNameStart >= 0) {
102          sourceName = sourceName.substring(qualifiedSourceNameStart + 1);
103        }
104    
105        return sourceName;
106      }
107    
108      /**
109       * Returns true if we will need get/set method pair for a field. These are
110       * required when the field would not be accessible to the serializer class.
111       */
112      private boolean needsAccessorMethods(JField field) {
113        boolean needsAccessors = !field.isPublic();
114        return needsAccessors;
115      }
116    
117      /**
118       * Return an array of fields declared by this class that are serializable
119       */
120      private JField[] applyFieldSerializationPolicy() {
121        if (fSerializableFields != null) {
122          return fSerializableFields;
123        }
124    
125        fSerializableFields = fSerializationOracle
126          .applyFieldSerializationPolicy(getSerializableClass());
127        return fSerializableFields;
128      }
129    
130      /**
131       * This method will generate a native JSNI accessor method for every field
132       * that is protected, private using the "Violator" pattern to allow an
133       * external class to access the field's value.
134       */
135      private void writeFieldAccessors() {
136        JField[] serializableFields = applyFieldSerializationPolicy();
137        int fieldCount = serializableFields.length;
138    
139        for (int fieldIndex = 0; fieldIndex < fieldCount; ++fieldIndex) {
140          JField serializableField = serializableFields[fieldIndex];
141    
142          if (!needsAccessorMethods(serializableField)) {
143            continue;
144          }
145    
146          writeFieldGet(serializableField);
147          writeFieldSet(serializableField);
148        }
149      }
150    
151      /**
152       * Write a setter method for an instance field.
153       */
154      private void writeFieldSet(JField serializableField) {
155        JType fieldType = serializableField.getType();
156        String fieldTypeQualifiedSourceName = fieldType.getQualifiedSourceName();
157        String serializableClassQualifedName = getSerializableClass()
158          .getQualifiedSourceName();
159        String fieldName = serializableField.getName();
160    
161        fSourceWriter.print("private static native void ");
162        fSourceWriter.print(" set");
163        fSourceWriter.print(capitalize(fieldName));
164        fSourceWriter.print("(");
165        fSourceWriter.print(serializableClassQualifedName);
166        fSourceWriter.print(" instance, ");
167        fSourceWriter.print(fieldTypeQualifiedSourceName);
168        fSourceWriter.println(" value) /*-{");
169        fSourceWriter.indent();
170    
171        fSourceWriter.print("instance.@");
172        fSourceWriter.print(serializableClassQualifedName);
173        fSourceWriter.print("::");
174        fSourceWriter.print(fieldName);
175        fSourceWriter.println(" = value;");
176    
177        fSourceWriter.outdent();
178        fSourceWriter.println("}-*/;");
179        fSourceWriter.println();
180      }
181    
182      /**
183       * Write a getter method for an instance field.
184       */
185      private void writeFieldGet(JField serializableField) {
186        JType fieldType = serializableField.getType();
187        String fieldTypeQualifiedSourceName = fieldType.getQualifiedSourceName();
188        String serializableClassQualifedName = getSerializableClass()
189          .getQualifiedSourceName();
190        String fieldName = serializableField.getName();
191    
192        fSourceWriter.print("private static native ");
193        fSourceWriter.print(fieldTypeQualifiedSourceName);
194        fSourceWriter.print(" get");
195        fSourceWriter.print(capitalize(fieldName));
196        fSourceWriter.print("(");
197        fSourceWriter.print(getSerializableClass().getQualifiedSourceName());
198        fSourceWriter.println(" instance) /*-{");
199        fSourceWriter.indent();
200    
201        fSourceWriter.print("return instance.@");
202        fSourceWriter.print(serializableClassQualifedName);
203        fSourceWriter.print("::");
204        fSourceWriter.print(fieldName);
205        fSourceWriter.println(";");
206    
207        fSourceWriter.outdent();
208        fSourceWriter.println("}-*/;");
209        fSourceWriter.println();
210      }
211    
212      private String capitalize(String name) {
213        String s = name.substring(0, 1).toUpperCase() + name.substring(1);
214        return s;
215      }
216    
217      private void writeDeserializeMethod(GeneratorContext ctx) {
218        fSourceWriter.print("public static void deserialize(");
219        fSourceWriter.print(SerializationStreamReader.class.getName());
220        fSourceWriter.print(" streamReader, ");
221        fSourceWriter.print(getSerializableClass().getQualifiedSourceName());
222        fSourceWriter.println(" instance) throws "
223          + Shared.SERIALIZATION_EXCEPTION_CLASS_NAME + "{");
224        fSourceWriter.indent();
225    
226        writeFieldDeserializationStatements(ctx);
227    
228        JClassType serializableClass = getSerializableClass().isClass();
229        if (serializableClass != null) {
230          JClassType serializableSuperClass = getSerializableSuperclass(serializableClass);
231          if (serializableSuperClass != null) {
232            String fieldSerializerName = fSerializationOracle
233              .getFieldSerializerName(serializableSuperClass);
234            fSourceWriter.print(fieldSerializerName);
235            fSourceWriter.println(".deserialize(streamReader, instance);");
236          }
237        }
238    
239        fSourceWriter.outdent();
240        fSourceWriter.println("}");
241        fSourceWriter.println();
242      }
243    
244      /**
245       * Given a class return the next superclass that is serializable.
246       * 
247       * @param serializableClass
248       * @return
249       */
250      private JClassType getSerializableSuperclass(JClassType serializableClass) {
251        if (serializableClass == null) {
252          return null;
253        }
254    
255        JClassType superClass = serializableClass.getSuperclass();
256        if (superClass != null) {
257          SerializableTypeOracle serializationOracle = getSerializationOracle();
258          if (serializationOracle.isSerializable(superClass)) {
259            return superClass;
260          }
261    
262          return getSerializableSuperclass(superClass);
263        }
264    
265        return null;
266      }
267    
268      private void writeFieldDeserializationStatements(GeneratorContext ctx) {
269        JType type = getSerializableClass();
270        JArrayType isArray = type.isArray();
271        if (isArray != null) {
272          fSourceWriter
273            .println("for (int itemIndex = 0; itemIndex < instance.length; ++itemIndex) {");
274          fSourceWriter.indent();
275    
276          JType componentType = isArray.getComponentType();
277          JType normalizedType = normalizeFieldType(ctx, componentType);
278          fSourceWriter.print("instance[itemIndex] = ");
279          if (normalizedType != componentType) {
280            fSourceWriter
281              .print("(" + componentType.getQualifiedSourceName() + ") ");
282          }
283          String readMethodName = "streamReader.read"
284            + capitalize(normalizedType.getSimpleSourceName());
285          fSourceWriter.println(readMethodName + "();");
286          fSourceWriter.outdent();
287          fSourceWriter.println("}");
288        } else {
289          JField[] serializableFields = applyFieldSerializationPolicy();
290          int fieldCount = serializableFields.length;
291    
292          for (int fieldIndex = 0; fieldIndex < fieldCount; ++fieldIndex) {
293            JField serializableField = serializableFields[fieldIndex];
294            JType fieldType = serializableField.getType();
295            JType normalizedFieldType = normalizeFieldType(ctx, fieldType);
296            boolean needsAccessor = needsAccessorMethods(serializableField);
297            boolean needsCast = fieldType != normalizedFieldType;
298    
299            String readMethodName = "read"
300              + capitalize(normalizedFieldType.getSimpleSourceName());
301            String streamReadExpression = "streamReader." + readMethodName + "()";
302            if (needsCast) {
303              streamReadExpression = "(" + fieldType.getQualifiedSourceName()
304                + ") " + streamReadExpression;
305            }
306    
307            if (needsAccessor) {
308              fSourceWriter.print("set");
309              fSourceWriter.print(capitalize(serializableField.getName()));
310              fSourceWriter.print("(instance, ");
311              fSourceWriter.print(streamReadExpression);
312              fSourceWriter.println(");");
313            } else {
314              fSourceWriter.print("instance.");
315              fSourceWriter.print(serializableField.getName());
316              fSourceWriter.print(" = ");
317              fSourceWriter.print(streamReadExpression);
318              fSourceWriter.println(";");
319            }
320          }
321        }
322    
323        fSourceWriter.println();
324      }
325    
326      private void writeSerializeMethod(GeneratorContext ctx) {
327        fSourceWriter.print("public static void serialize(");
328        fSourceWriter.print(SerializationStreamWriter.class.getName());
329        fSourceWriter.print(" streamWriter, ");
330        fSourceWriter.print(getSerializableClass().getQualifiedSourceName());
331        fSourceWriter.println(" instance) throws "
332          + Shared.SERIALIZATION_EXCEPTION_CLASS_NAME + " {");
333        fSourceWriter.indent();
334    
335        writeFieldSerializationStatements(ctx);
336    
337        JClassType serializableClass = getSerializableClass().isClass();
338        if (serializableClass != null) {
339          JClassType serializableSuperclass = getSerializableSuperclass(serializableClass);
340          if (serializableSuperclass != null) {
341            String fieldSerializerName = fSerializationOracle
342              .getFieldSerializerName(serializableSuperclass);
343            fSourceWriter.print(fieldSerializerName);
344            fSourceWriter.println(".serialize(streamWriter, instance);");
345          }
346        }
347    
348        fSourceWriter.outdent();
349        fSourceWriter.println("}");
350        fSourceWriter.println();
351      }
352    
353      private void writeFieldSerializationStatements(GeneratorContext ctx) {
354        JType type = getSerializableClass();
355        JArrayType isArray = type.isArray();
356        if (isArray != null) {
357          fSourceWriter.println("int itemCount = instance.length;");
358          fSourceWriter.println();
359          fSourceWriter.println("streamWriter.writeInt(itemCount);");
360          fSourceWriter.println();
361          fSourceWriter
362            .println("for (int itemIndex = 0; itemIndex < itemCount; ++itemIndex) {");
363          fSourceWriter.indent();
364          JType normalizedType = normalizeFieldType(ctx, isArray.getComponentType());
365          String writeMethodName = "write"
366            + capitalize(normalizedType.getSimpleSourceName());
367          fSourceWriter.print("streamWriter.");
368          fSourceWriter.print(writeMethodName);
369          fSourceWriter.println("(instance[itemIndex]);");
370          fSourceWriter.outdent();
371          fSourceWriter.println("}");
372        } else {
373          JField[] serializableFields = applyFieldSerializationPolicy();
374          int fieldCount = serializableFields.length;
375    
376          for (int fieldIndex = 0; fieldIndex < fieldCount; ++fieldIndex) {
377            JField serializableField = serializableFields[fieldIndex];
378            JType fieldType = serializableField.getType();
379            JType normalizedType = normalizeFieldType(ctx, fieldType);
380    
381            String writeMethodName = "write"
382              + capitalize(normalizedType.getSimpleSourceName());
383            fSourceWriter.print("streamWriter.");
384            fSourceWriter.print(writeMethodName);
385            fSourceWriter.print("(");
386    
387            if (needsAccessorMethods(serializableField)) {
388              fSourceWriter.print("get");
389              fSourceWriter.print(capitalize(serializableField.getName()));
390              fSourceWriter.println("(instance));");
391            } else {
392              fSourceWriter.print("instance.");
393              fSourceWriter.print(serializableField.getName());
394              fSourceWriter.println(");");
395            }
396          }
397        }
398    
399        fSourceWriter.println();
400      }
401    
402      /*
403       * Given a field's type return the type that can be handled via the
404       * SerializationStreamReader and SerializationStreamWriter interfaces.
405       */
406      private JType normalizeFieldType(GeneratorContext ctx, JType fieldType) {
407        assert (fieldType != null);
408    
409        if (fieldType.isPrimitive() == null) {
410          TypeOracle typeOracle = ctx.getTypeOracle();
411          if (fObjectType == null) {
412            // Lazily init & cache JClassType for java.lang.Object
413            fObjectType = typeOracle.findType("java.lang.Object");
414            
415            if (fObjectType == null) {
416              throw new RuntimeException("Unable to locate the definition of java.lang.Object");
417            }
418          }
419          
420          fieldType = fObjectType;
421        }
422    
423        return fieldType;
424      }
425    
426      private SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext ctx)
427          throws IOException {
428        String packageName = getSerializableClassPackageName();
429        String className = getSimpleSourceName(getSerializableClass());
430    
431        PrintWriter printWriter = ctx.tryCreate(logger, packageName, className);
432        if (printWriter == null) {
433          return null;
434        }
435    
436        ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
437          packageName, className);
438    
439        return composerFactory.createSourceWriter(ctx, printWriter);
440      }
441    
442      private String getSerializableClassPackageName() {
443        JType type = getSerializableClass();
444        String packageName = fSerializationOracle.getFieldSerializerName(type);
445        int packageNameEnd = packageName.lastIndexOf('.');
446        if (packageNameEnd >= 0) {
447          packageName = packageName.substring(0, packageNameEnd);
448        }
449    
450        return packageName;
451      }
452    
453      private SerializableTypeOracle getSerializationOracle() {
454        return fSerializationOracle;
455      }
456    
457      private void setSerializationOracle(SerializableTypeOracle serializationOracle) {
458        fSerializationOracle = serializationOracle;
459      }
460    }