• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.auto.value.processor;
17 
18 import static java.nio.charset.StandardCharsets.UTF_8;
19 import static java.util.stream.Collectors.toList;
20 
21 import com.google.auto.common.AnnotationMirrors;
22 import com.google.auto.value.processor.AutoValueishProcessor.GetterProperty;
23 import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
24 import com.google.common.collect.ImmutableMap;
25 import com.google.common.collect.Multimap;
26 import com.google.escapevelocity.Template;
27 import java.io.IOException;
28 import java.io.Writer;
29 import java.util.List;
30 import java.util.Optional;
31 import java.util.zip.CRC32;
32 import javax.annotation.processing.ProcessingEnvironment;
33 import javax.lang.model.element.AnnotationMirror;
34 import javax.lang.model.element.TypeElement;
35 import javax.lang.model.type.TypeMirror;
36 import javax.tools.Diagnostic;
37 import javax.tools.JavaFileObject;
38 
39 /**
40  * Generates GWT serialization code for {@code @AutoValue} classes also marked
41  * {@code @GwtCompatible(serializable = true)}.
42  *
43  * @author Éamonn McManus
44  */
45 class GwtSerialization {
46   private final GwtCompatibility gwtCompatibility;
47   private final ProcessingEnvironment processingEnv;
48   private final TypeElement type;
49 
GwtSerialization( GwtCompatibility gwtCompatibility, ProcessingEnvironment processingEnv, TypeElement type)50   GwtSerialization(
51       GwtCompatibility gwtCompatibility, ProcessingEnvironment processingEnv, TypeElement type) {
52     this.gwtCompatibility = gwtCompatibility;
53     this.processingEnv = processingEnv;
54     this.type = type;
55   }
56 
shouldWriteGwtSerializer()57   private boolean shouldWriteGwtSerializer() {
58     Optional<AnnotationMirror> optionalGwtCompatible = gwtCompatibility.gwtCompatibleAnnotation();
59     if (optionalGwtCompatible.isPresent()) {
60       AnnotationMirror gwtCompatible = optionalGwtCompatible.get();
61       return AnnotationMirrors.getAnnotationValuesWithDefaults(gwtCompatible).entrySet().stream()
62           .anyMatch(
63               e ->
64                   e.getKey().getSimpleName().contentEquals("serializable")
65                       && e.getValue().getValue().equals(true));
66     }
67     return false;
68   }
69 
70   /**
71    * Writes the GWT serializer for the given type, if appropriate. An {@code @AutoValue} class gets
72    * a GWT serializer if it is annotated with {@code @GwtCompatible(serializable = true)}, where the
73    * {@code @GwtCompatible} annotation can come from any package.
74    *
75    * <p>If the type is com.example.Foo then the generated AutoValue subclass is
76    * com.example.AutoValue_Foo and the GWT serializer is
77    * com.example.AutoValue_Foo_CustomFieldSerializer.
78    *
79    * @param autoVars the template variables defined for this type.
80    * @param finalSubclass the simple name of the AutoValue class being generated, AutoValue_Foo
81    *     in the example.
82    */
maybeWriteGwtSerializer(AutoValueTemplateVars autoVars, String finalSubclass)83   void maybeWriteGwtSerializer(AutoValueTemplateVars autoVars, String finalSubclass) {
84     if (shouldWriteGwtSerializer()) {
85       GwtTemplateVars vars = new GwtTemplateVars();
86       vars.pkg = autoVars.pkg;
87       vars.subclass = finalSubclass;
88       vars.formalTypes = autoVars.formalTypes;
89       vars.actualTypes = autoVars.actualTypes;
90       vars.useBuilder = !autoVars.builderTypeName.isEmpty();
91       vars.builderSetters = autoVars.builderSetters;
92       vars.builderPropertyBuilders = autoVars.builderPropertyBuilders;
93       vars.generated = autoVars.generated;
94       String className =
95           (vars.pkg.isEmpty() ? "" : vars.pkg + ".") + vars.subclass + "_CustomFieldSerializer";
96       vars.serializerClass = TypeSimplifier.simpleNameOf(className);
97       vars.props =
98           autoVars.props.stream().map(p -> new Property((GetterProperty) p)).collect(toList());
99       vars.classHashString = computeClassHash(autoVars.props, vars.pkg);
100       String text = vars.toText();
101       text = TypeEncoder.decode(text, processingEnv, vars.pkg, type.asType());
102       writeSourceFile(className, text, type);
103     }
104   }
105 
106   public static class Property {
107     private final GetterProperty property;
108     private final boolean isCastingUnchecked;
109 
Property(GetterProperty property)110     Property(GetterProperty property) {
111       this.property = property;
112       this.isCastingUnchecked = TypeSimplifier.isCastingUnchecked(property.getTypeMirror());
113     }
114 
115     @Override
toString()116     public String toString() {
117       return property.toString();
118     }
119 
getGetter()120     public String getGetter() {
121       return property.getGetter();
122     }
123 
getType()124     public String getType() {
125       return property.getType();
126     }
127 
getName()128     public String getName() {
129       return property.getName();
130     }
131 
132     /**
133      * Returns the suffix in serializer method names for values of the given type. For example, if
134      * the type is "int" then the returned value will be "Int" because the serializer methods are
135      * called readInt and writeInt. There are methods for all primitive types and String; every
136      * other type uses readObject and writeObject.
137      */
getGwtType()138     public String getGwtType() {
139       TypeMirror typeMirror = property.getTypeMirror();
140       String type = typeMirror.toString();
141       if (property.getKind().isPrimitive()) {
142         return Character.toUpperCase(type.charAt(0)) + type.substring(1);
143       } else if (type.equals("java.lang.String")) {
144         return "String";
145       } else {
146         return "Object";
147       }
148     }
149 
150     /**
151      * Returns a string to be inserted before the call to the readFoo() call so that the expression
152      * can be assigned to the given type. For primitive types and String, the readInt() etc methods
153      * already return the right type so the string is empty. For other types, the string is a cast
154      * like "(Foo) ".
155      */
getGwtCast()156     public String getGwtCast() {
157       if (property.getKind().isPrimitive() || getType().equals("String")) {
158         return "";
159       } else {
160         return "(" + getType() + ") ";
161       }
162     }
163 
isCastingUnchecked()164     public boolean isCastingUnchecked() {
165       return isCastingUnchecked;
166     }
167   }
168 
169   @SuppressWarnings("unused") // some fields are only read through reflection
170   static class GwtTemplateVars extends TemplateVars {
171     /** The properties defined by the parent class's abstract methods. */
172     List<Property> props;
173 
174     /**
175      * The package of the class with the {@code @AutoValue} annotation and its generated subclass.
176      */
177     String pkg;
178 
179     /** The simple name of the generated subclass. */
180     String subclass;
181 
182     /**
183      * The formal generic signature of the class with the {@code @AutoValue} annotation and its
184      * generated subclass. This is empty, or contains type variables with optional bounds, for
185      * example {@code <K, V extends K>}.
186      */
187     String formalTypes;
188     /**
189      * The generic signature used by the generated subclass for its superclass reference. This is
190      * empty, or contains only type variables with no bounds, for example {@code <K, V>}.
191      */
192     String actualTypes;
193 
194     /** True if the {@code @AutoValue} class is constructed using a generated builder. */
195     Boolean useBuilder;
196 
197     /**
198      * A multimap from property names (like foo) to the corresponding setter methods (foo or
199      * setFoo).
200      */
201     Multimap<String, BuilderSpec.PropertySetter> builderSetters;
202 
203     /**
204      * A map from property names to information about the associated property builder. A property
205      * called foo (defined by a method foo() or getFoo()) can have a property builder called
206      * fooBuilder(). The type of foo must be a type that has an associated builder following certain
207      * conventions. Guava immutable types such as ImmutableList follow those conventions, as do many
208      * {@code @AutoValue} types.
209      */
210     ImmutableMap<String, PropertyBuilder> builderPropertyBuilders = ImmutableMap.of();
211 
212     /** The simple name of the generated GWT serializer class. */
213     String serializerClass;
214 
215     /**
216      * The encoding of the {@code Generated} class. Empty if no {@code Generated} class is
217      * available.
218      */
219     String generated;
220 
221     /** A string that should change if any salient details of the serialized class change. */
222     String classHashString;
223 
224     private static final Template TEMPLATE = parsedTemplateForResource("gwtserializer.vm");
225 
226     @Override
parsedTemplate()227     Template parsedTemplate() {
228       return TEMPLATE;
229     }
230   }
231 
writeSourceFile(String className, String text, TypeElement originatingType)232   private void writeSourceFile(String className, String text, TypeElement originatingType) {
233     try {
234       JavaFileObject sourceFile =
235           processingEnv.getFiler().createSourceFile(className, originatingType);
236       try (Writer writer = sourceFile.openWriter()) {
237         writer.write(text);
238       }
239     } catch (IOException e) {
240       processingEnv
241           .getMessager()
242           .printMessage(
243               Diagnostic.Kind.WARNING, "Could not write generated class " + className + ": " + e);
244       // A warning rather than an error for the reason explained in
245       // AutoValueishProcessor.writeSourceFile.
246     }
247   }
248 
249   // Compute a hash that is guaranteed to change if the names, types, or order of the fields
250   // change. We use TypeEncoder so that we can get a defined string for types, since
251   // TypeMirror.toString() isn't guaranteed to remain the same.
computeClassHash(Iterable<AutoValueishProcessor.Property> props, String pkg)252   private String computeClassHash(Iterable<AutoValueishProcessor.Property> props, String pkg) {
253     CRC32 crc = new CRC32();
254     String encodedType = TypeEncoder.encode(type.asType()) + ":";
255     String decodedType = TypeEncoder.decode(encodedType, processingEnv, "", null);
256     if (!decodedType.startsWith(pkg)) {
257       // This is for compatibility with the way an earlier version did things. Preserving hash
258       // codes probably isn't vital, since client and server should be in sync.
259       decodedType = pkg + "." + decodedType;
260     }
261     crc.update(decodedType.getBytes(UTF_8));
262     for (AutoValueishProcessor.Property prop : props) {
263       String encodedProp = prop + ":" + TypeEncoder.encode(prop.getTypeMirror()) + ";";
264       String decodedProp = TypeEncoder.decode(encodedProp, processingEnv, pkg, null);
265       crc.update(decodedProp.getBytes(UTF_8));
266     }
267     return String.format("%08x", crc.getValue());
268   }
269 }
270