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