Skip to content

Commit

Permalink
Add @AutoCodec strategy for @AutoValue.Builder.
Browse files Browse the repository at this point in the history
Still missing a lot of features (most notably the ability to construct an iterable element by element), but this is ok for now.

PiperOrigin-RevId: 189410898
  • Loading branch information
janakdr authored and Copybara-Service committed Mar 16, 2018
1 parent 706bdb3 commit f304d2b
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.Strategy;
import javax.annotation.concurrent.Immutable;

/**
* A {@link TransitiveInfoProvider} that aggregates {@link ProtoJavaApiInfoProvider} for propogation
* in an aspect.
*/
@AutoCodec(strategy = Strategy.AUTO_VALUE_BUILDER)
@Immutable
@AutoValue
public abstract class ProtoJavaApiInfoAspectProvider implements TransitiveInfoProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ enum Strategy {
* instance for deserialization.
*/
PUBLIC_FIELDS,
/**
* For use with {@link com.google.auto.value.AutoValue} classes with an {@link
* com.google.auto.value.AutoValue.Builder} static nested Builder class: uses the builder when
* deserializing.
*/
AUTO_VALUE_BUILDER,
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.skyframe.serialization.CodecScanningConstants;
import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
Expand All @@ -33,6 +34,7 @@
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -110,6 +112,9 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
case PUBLIC_FIELDS:
codecClassBuilder = buildClassWithPublicFieldsStrategy(encodedType, startMemoizing);
break;
case AUTO_VALUE_BUILDER:
codecClassBuilder = buildClassWithAutoValueBuilderStrategy(encodedType, startMemoizing);
break;
default:
throw new IllegalArgumentException("Unknown strategy: " + annotation.strategy());
}
Expand Down Expand Up @@ -188,7 +193,37 @@ private TypeSpec.Builder buildClassWithInstantiatorStrategy(
MethodSpec.Builder deserializeBuilder =
AutoCodecUtil.initializeDeserializeMethodBuilder(encodedType, startMemoizing, env);
buildDeserializeBody(deserializeBuilder, fields);
addReturnNew(deserializeBuilder, encodedType, constructor, startMemoizing, env);
addReturnNew(
deserializeBuilder, encodedType, constructor, /*builderVar=*/ null, startMemoizing, env);
codecClassBuilder.addMethod(deserializeBuilder.build());

return codecClassBuilder;
}

private TypeSpec.Builder buildClassWithAutoValueBuilderStrategy(
TypeElement encodedType, boolean startMemoizing) {
TypeElement builderType = findBuilderType(encodedType);
List<ExecutableElement> getters = findGettersFromType(encodedType);
ExecutableElement builderCreationMethod = findBuilderCreationMethod(encodedType, builderType);
ExecutableElement buildMethod = findBuildMethod(encodedType, builderType);
TypeSpec.Builder codecClassBuilder =
AutoCodecUtil.initializeCodecClassBuilder(encodedType, env);
MethodSpec.Builder serializeBuilder =
AutoCodecUtil.initializeSerializeMethodBuilder(encodedType, startMemoizing, env);
for (ExecutableElement getter : getters) {
marshallers.writeSerializationCode(
new Marshaller.Context(
serializeBuilder,
getter.getReturnType(),
turnGetterIntoExpression(getter.getSimpleName().toString())));
}
codecClassBuilder.addMethod(serializeBuilder.build());
MethodSpec.Builder deserializeBuilder =
AutoCodecUtil.initializeDeserializeMethodBuilder(encodedType, startMemoizing, env);
String builderVarName =
buildDeserializeBodyWithBuilder(
encodedType, builderType, deserializeBuilder, getters, builderCreationMethod);
addReturnNew(deserializeBuilder, encodedType, buildMethod, builderVarName, startMemoizing, env);
codecClassBuilder.addMethod(deserializeBuilder.build());

return codecClassBuilder;
Expand Down Expand Up @@ -228,6 +263,211 @@ private static boolean hasInstantiatorAnnotation(Element elt) {
return elt.getAnnotation(AutoCodec.Instantiator.class) != null;
}

private TypeElement findBuilderType(TypeElement encodedType) {
TypeElement builderType = null;
for (Element element : encodedType.getEnclosedElements()) {
if (element instanceof TypeElement
&& element.getModifiers().contains(Modifier.STATIC)
&& element.getAnnotation(AutoValue.Builder.class) != null) {
if (builderType != null) {
throw new IllegalArgumentException(
"Type "
+ encodedType
+ " had multiple inner classes annotated as @AutoValue.Builder: "
+ builderType
+ " and "
+ element);
}
builderType = (TypeElement) element;
}
}
return builderType;
}

private List<ExecutableElement> findGettersFromType(TypeElement encodedType) {
List<ExecutableElement> result = new ArrayList<>();
for (ExecutableElement method :
ElementFilter.methodsIn(env.getElementUtils().getAllMembers(encodedType))) {
if (!method.getModifiers().contains(Modifier.STATIC)
&& method.getModifiers().contains(Modifier.ABSTRACT)
&& method.getParameters().isEmpty()
&& method.getReturnType().getKind() != TypeKind.VOID) {
result.add(method);
}
}
if (result.isEmpty()) {
throw new IllegalArgumentException("Couldn't find any properties for " + encodedType);
}
return result;
}

private String getNameFromGetter(ExecutableElement method) {
String name = method.getSimpleName().toString();
if (name.startsWith("get")) {
return name.substring(3, 4).toLowerCase() + name.substring(4);
} else {
return name;
}
}

private ExecutableElement findBuilderCreationMethod(
TypeElement encodedType, TypeElement builderType) {
ExecutableElement builderMethod = null;
for (ExecutableElement method :
ElementFilter.methodsIn(env.getElementUtils().getAllMembers(encodedType))) {
if (method.getModifiers().contains(Modifier.STATIC)
&& !method.getModifiers().contains(Modifier.ABSTRACT)
&& method.getParameters().isEmpty()
&& method.getReturnType().equals(builderType.asType())) {
if (builderMethod != null) {
throw new IllegalArgumentException(
"Type "
+ encodedType
+ " had multiple static methods to create an element of type "
+ builderType
+ ": "
+ builderMethod
+ " and "
+ method);
}
builderMethod = method;
}
}
if (builderMethod == null) {
throw new IllegalArgumentException(
"Couldn't find builder creation method for " + encodedType + " and " + builderType);
}
return builderMethod;
}

private ExecutableElement findBuildMethod(TypeElement encodedType, TypeElement builderType) {
ExecutableElement abstractBuildMethod = null;
ExecutableElement concreteBuildMethod = null;
for (ExecutableElement method :
ElementFilter.methodsIn(env.getElementUtils().getAllMembers(builderType))) {
if (method.getModifiers().contains(Modifier.STATIC)) {
continue;
}
if (method.getParameters().isEmpty() && method.getReturnType().equals(encodedType.asType())) {
if (method.getModifiers().contains(Modifier.ABSTRACT)) {
if (abstractBuildMethod != null) {
throw new IllegalArgumentException(
"Type "
+ builderType
+ " had multiple abstract methods to create an element of type "
+ encodedType
+ ": "
+ abstractBuildMethod
+ " and "
+ method);
}
abstractBuildMethod = method;
} else {
if (concreteBuildMethod != null) {
throw new IllegalArgumentException(
"Type "
+ builderType
+ " had multiple concrete methods to create an element of type "
+ encodedType
+ ": "
+ concreteBuildMethod
+ " and "
+ method);
}
concreteBuildMethod = method;
}
}
}
if (abstractBuildMethod == null && concreteBuildMethod == null) {
throw new IllegalArgumentException(
"Couldn't find build method for " + encodedType + " and " + builderType);
}
return abstractBuildMethod == null ? concreteBuildMethod : abstractBuildMethod;
}

private String buildDeserializeBodyWithBuilder(
TypeElement encodedType,
TypeElement builderType,
MethodSpec.Builder builder,
List<ExecutableElement> fields,
ExecutableElement builderCreationMethod) {
String builderVarName = "objectBuilder";
builder.addStatement(
"$T $L = $T.$L()",
builderCreationMethod.getReturnType(),
builderVarName,
encodedType,
builderCreationMethod.getSimpleName());
for (ExecutableElement getter : fields) {
String paramName = getNameFromGetter(getter) + "_";
marshallers.writeDeserializationCode(
new Marshaller.Context(builder, getter.getReturnType(), paramName));
setValueInBuilder(builderType, getter, paramName, builderVarName, builder);
}
return builderVarName;
}

private void setValueInBuilder(
TypeElement builderType,
ExecutableElement getter,
String paramName,
String builderVarName,
MethodSpec.Builder methodBuilder) {
ExecutableElement setterMethod = findSetterGivenGetter(getter, builderType);
methodBuilder.addStatement(
"$L.$L($L)", builderVarName, setterMethod.getSimpleName(), paramName);
}

private ExecutableElement findSetterGivenGetter(
ExecutableElement getter, TypeElement builderType) {
List<ExecutableElement> methods =
ElementFilter.methodsIn(env.getElementUtils().getAllMembers(builderType));
String varName = getNameFromGetter(getter);
TypeMirror type = getter.getReturnType();

ImmutableSet.Builder<String> possibleSetterNamesBuilder =
ImmutableSet.<String>builder().add(addCamelCasePrefix(varName, "set"));

if (AutoCodecUtil.isSubType(type, Iterable.class, env)) {
possibleSetterNamesBuilder.add(addCamelCasePrefix(varName, "add"));
}
ImmutableSet<String> possibleSetterNames = possibleSetterNamesBuilder.build();

ExecutableElement setterMethod = null;
for (ExecutableElement method : methods) {
if (!method.getModifiers().contains(Modifier.STATIC)
&& !method.getModifiers().contains(Modifier.PRIVATE)
&& possibleSetterNames.contains(method.getSimpleName().toString())
&& method.getReturnType().equals(builderType.asType())
&& method.getParameters().size() == 1
&& env.getTypeUtils()
.isSubtype(type, Iterables.getOnlyElement(method.getParameters()).asType())) {
if (setterMethod != null) {
throw new IllegalArgumentException(
"Multiple setter methods for "
+ getter
+ " found in "
+ builderType
+ ": "
+ setterMethod
+ " and "
+ method);
}
setterMethod = method;
}
}
if (setterMethod != null) {
return setterMethod;
}

throw new IllegalArgumentException(
builderType
+ ": No setter found corresponding to getter "
+ getter.getSimpleName()
+ ", "
+ type);
}

private enum Relation {
INSTANCE_OF,
EQUAL_TO,
Expand Down Expand Up @@ -346,8 +586,8 @@ private String findGetterForClass(VariableElement parameter, TypeElement type) {
List<ExecutableElement> methods =
ElementFilter.methodsIn(env.getElementUtils().getAllMembers(type));

ImmutableList.Builder<String> possibleGetterNamesBuilder =
ImmutableList.<String>builder().add(parameter.getSimpleName().toString());
ImmutableSet.Builder<String> possibleGetterNamesBuilder =
ImmutableSet.<String>builder().add(parameter.getSimpleName().toString());

if (parameter.asType().getKind() == TypeKind.BOOLEAN) {
possibleGetterNamesBuilder.add(
Expand All @@ -356,7 +596,7 @@ private String findGetterForClass(VariableElement parameter, TypeElement type) {
possibleGetterNamesBuilder.add(
addCamelCasePrefix(parameter.getSimpleName().toString(), "get"));
}
ImmutableList<String> possibleGetterNames = possibleGetterNamesBuilder.build();
ImmutableSet<String> possibleGetterNames = possibleGetterNamesBuilder.build();

for (ExecutableElement element : methods) {
if (!element.getModifiers().contains(Modifier.STATIC)
Expand Down Expand Up @@ -386,11 +626,15 @@ private static String firstLetterUpper(String str) {

private void addSerializeParameterWithGetter(
TypeElement encodedType, VariableElement parameter, MethodSpec.Builder serializeBuilder) {
String getter = "input." + findGetterForClass(parameter, encodedType) + "()";
String getter = turnGetterIntoExpression(findGetterForClass(parameter, encodedType));
marshallers.writeSerializationCode(
new Marshaller.Context(serializeBuilder, parameter.asType(), getter));
}

private static String turnGetterIntoExpression(String getterName) {
return "input." + getterName + "()";
}

private MethodSpec buildSerializeMethodWithInstantiatorForAutoValue(
TypeElement encodedType, List<? extends VariableElement> fields, boolean startMemoizing) {
MethodSpec.Builder serializeBuilder =
Expand Down Expand Up @@ -465,6 +709,7 @@ private static void addReturnNew(
MethodSpec.Builder builder,
TypeElement type,
ExecutableElement instantiator,
Object builderVar,
boolean startMemoizing,
ProcessingEnvironment env) {
// TODO(janakr): When injection of additional data and memoizing are separated, rework this so
Expand All @@ -485,8 +730,11 @@ private static void addReturnNew(
.collect(Collectors.joining(", "));
if (instantiator.getKind().equals(ElementKind.CONSTRUCTOR)) {
builder.addStatement("return new $T($L)", typeName, parameters);
} else { // Otherwise, it's a factory method.
} else if (builderVar == null) { // Otherwise, it's a factory method.
builder.addStatement("return $T.$L($L)", typeName, instantiator.getSimpleName(), parameters);
} else {
builder.addStatement(
"return $L.$L($L)", builderVar, instantiator.getSimpleName(), parameters);
}
if (!allThrown.isEmpty()) {
for (TypeMirror thrown : allThrown) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;

/** Static utilities for AutoCodec processors. */
class AutoCodecUtil {
Expand Down Expand Up @@ -141,4 +142,12 @@ static String getGeneratedName(Element element, String suffix) {
private static String getCodecName(Element element) {
return getGeneratedName(element, GENERATED_CLASS_NAME_SUFFIX);
}

static TypeMirror getType(Class<?> clazz, ProcessingEnvironment env) {
return env.getElementUtils().getTypeElement((clazz.getCanonicalName())).asType();
}

static boolean isSubType(TypeMirror type, Class<?> clazz, ProcessingEnvironment env) {
return env.getTypeUtils().isSubtype(type, getType(clazz, env));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ private boolean matchesErased(TypeMirror type, Class<?> clazz) {

/** Returns the TypeMirror corresponding to {@code clazz}. */
private TypeMirror getType(Class<?> clazz) {
return env.getElementUtils().getTypeElement((clazz.getCanonicalName())).asType();
return AutoCodecUtil.getType(clazz, env);
}

static boolean isVariableOrWildcardType(TypeMirror type) {
Expand Down

0 comments on commit f304d2b

Please sign in to comment.