Skip to content

Commit

Permalink
Enables dependency injection for PUBLIC_FIELD and POLYMORPHIC AutoCod…
Browse files Browse the repository at this point in the history
…ec strategies.

PiperOrigin-RevId: 180954849
  • Loading branch information
aoeui authored and Copybara-Service committed Jan 5, 2018
1 parent 54177d7 commit d5d508d
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ java_library(
deps = [
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
"//third_party:guava",
"//third_party:jsr305",
"//third_party/protobuf:protobuf_java",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// 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.devtools.build.lib.skyframe.serialization;

import com.google.devtools.build.lib.skyframe.serialization.strings.StringCodecs;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Optional;
import javax.annotation.Nullable;

/** Helper methods for polymorphic codecs using reflection. */
public class PolymorphicHelper {

private PolymorphicHelper() {}

@SuppressWarnings("unchecked")
public static void serialize(Object input, CodedOutputStream codedOut)
throws IOException, SerializationException {
if (input != null) {
Class<?> clazz = input.getClass();
try {
Object codec = getCodec(clazz);
codedOut.writeBoolNoTag(true);
StringCodecs.asciiOptimized().serialize(clazz.getName(), codedOut);
if (codec instanceof ObjectCodec) {
((ObjectCodec) codec).serialize(input, codedOut);
} else if (codec instanceof InjectingObjectCodec) {
((InjectingObjectCodec) codec).serialize(input, codedOut);
} else {
throw new SerializationException(
clazz.getCanonicalName()
+ ".CODEC has unexpected type "
+ codec.getClass().getCanonicalName());
}
} catch (ReflectiveOperationException e) {
throw new SerializationException(input.getClass().getName(), e);
}
} else {
codedOut.writeBoolNoTag(false);
}
}

/**
* Deserializes a polymorphic type.
*
* @param dependency if null, it means that the parent, polymorphic type, provides no dependency.
* It is valid for dependency to be non-null, with an enclosed null value.
*/
@SuppressWarnings("unchecked")
public static Object deserialize(CodedInputStream codedIn, @Nullable Optional<?> dependency)
throws IOException, SerializationException {
Object deserialized = null;
if (codedIn.readBool()) {
String className = StringCodecs.asciiOptimized().deserialize(codedIn);
try {
Object codec = getCodec(Class.forName(className));
if (codec instanceof ObjectCodec) {
return ((ObjectCodec) codec).deserialize(codedIn);
} else if (codec instanceof InjectingObjectCodec) {
if (dependency == null) {
throw new SerializationException(
className + " deserialize parent class lacks required dependency.");
}
return ((InjectingObjectCodec) codec).deserialize(dependency.orElse(null), codedIn);
} else {
throw new SerializationException(
className + ".CODEC has unexpected type " + codec.getClass().getCanonicalName());
}
} catch (ReflectiveOperationException e) {
throw new SerializationException(className, e);
}
}
return deserialized;
}

/** Returns the static CODEC instance for {@code clazz}. */
private static Object getCodec(Class<?> clazz)
throws NoSuchFieldException, IllegalAccessException {
Field codecField = clazz.getDeclaredField("CODEC");
codecField.setAccessible(true);
return codecField.get(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ public static enum Strategy {
public static @interface Constructor {}

/**
* Marks a specific constructor parameter as a dependency when using the {@code CONSTRUCTOR}
* strategy.
* Marks a specific constructor parameter as a dependency.
*
* <p>When a constructor selected for the {@code CONSTRUCTOR} strategy has one of its parameters
* tagged {@code @Dependency}, {@code @AutoCodec} generates an {@link
Expand All @@ -99,4 +98,16 @@ public static enum Strategy {
public static @interface Dependency {}

Strategy strategy() default Strategy.CONSTRUCTOR;
/**
* Specifies a deserialization dependency.
*
* <p>When non-{@link Void}, generates an {@link
* com.google.devtools.build.lib.skyframe.serialization.InjectingObjectCodec} instead of the usual
* {@link com.google.devtools.build.lib.skyframe.serialization.ObjectCodec} with the dependency
* type parameter matching the returned type.
*
* <p>This is for use with {@code PUBLIC_FIELDS}, and {@code POLYMORPHIC} strategies. It is an
* error to use this with the {@code CONSTRUCTOR} strategy.
*/
Class<?> dependency() default Void.class;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,16 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
import com.google.devtools.build.lib.skyframe.serialization.PolymorphicHelper;
import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
import com.google.devtools.build.lib.skyframe.serialization.strings.StringCodecs;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
Expand All @@ -48,6 +44,7 @@
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
Expand Down Expand Up @@ -96,16 +93,23 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
for (Element element : roundEnv.getElementsAnnotatedWith(AutoCodecUtil.ANNOTATION)) {
AutoCodec annotation = element.getAnnotation(AutoCodecUtil.ANNOTATION);
TypeElement encodedType = (TypeElement) element;
@Nullable TypeElement dependencyType = getDependencyType(annotation);
TypeSpec.Builder codecClassBuilder = null;
switch (annotation.strategy()) {
case CONSTRUCTOR:
if (dependencyType != null) {
throw new IllegalArgumentException(
encodedType.getQualifiedName()
+ " uses the CONSTRUCTOR strategy and has a non-Void dependency "
+ dependencyType.getQualifiedName());
}
codecClassBuilder = buildClassWithConstructorStrategy(encodedType);
break;
case PUBLIC_FIELDS:
codecClassBuilder = buildClassWithPublicFieldsStrategy(encodedType);
codecClassBuilder = buildClassWithPublicFieldsStrategy(encodedType, dependencyType);
break;
case POLYMORPHIC:
codecClassBuilder = buildClassWithPolymorphicStrategy(encodedType);
codecClassBuilder = buildClassWithPolymorphicStrategy(encodedType, dependencyType);
break;
default:
throw new IllegalArgumentException("Unknown strategy: " + annotation.strategy());
Expand All @@ -131,6 +135,21 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
return true;
}

/** Returns the type of the annotation dependency or null if the type is {@link Void}. */
@Nullable
private TypeElement getDependencyType(AutoCodec annotation) {
try {
annotation.dependency();
throw new AssertionError("Expected MirroredTypeException!");
} catch (MirroredTypeException e) {
DeclaredType dependencyMirror = (DeclaredType) e.getTypeMirror();
if (matchesType(dependencyMirror, Void.class)) {
return null;
}
return (TypeElement) dependencyMirror.asElement();
}
}

private TypeSpec.Builder buildClassWithConstructorStrategy(TypeElement encodedType) {
ExecutableElement constructor = selectConstructorForConstructorStrategy(encodedType);
PartitionedParameters parameters = isolateDependency(constructor);
Expand Down Expand Up @@ -160,7 +179,7 @@ private static class PartitionedParameters {
*
* <p>Null if no such parameter exists.
*/
@Nullable VariableElement dependency;
@Nullable TypeElement dependency;
}

/** Separates any dependency from the constructor parameters. */
Expand All @@ -181,7 +200,7 @@ private static PartitionedParameters isolateDependency(ExecutableElement constru
+ " constructor has multiple Dependency annotations.");
}
if (!dependencies.isEmpty()) {
result.dependency = dependencies.get(0);
result.dependency = (TypeElement) ((DeclaredType) dependencies.get(0).asType()).asElement();
}
return result;
}
Expand Down Expand Up @@ -253,16 +272,18 @@ private MethodSpec buildSerializeMethodWithConstructor(
return serializeBuilder.build();
}

private TypeSpec.Builder buildClassWithPublicFieldsStrategy(TypeElement encodedType) {
TypeSpec.Builder codecClassBuilder = AutoCodecUtil.initializeCodecClassBuilder(encodedType);
private TypeSpec.Builder buildClassWithPublicFieldsStrategy(
TypeElement encodedType, @Nullable TypeElement dependency) {
TypeSpec.Builder codecClassBuilder =
AutoCodecUtil.initializeCodecClassBuilder(encodedType, dependency);
ImmutableList<? extends VariableElement> publicFields =
ElementFilter.fieldsIn(env.getElementUtils().getAllMembers(encodedType))
.stream()
.filter(this::isPublicField)
.collect(toImmutableList());
codecClassBuilder.addMethod(buildSerializeMethodWithPublicFields(encodedType, publicFields));
MethodSpec.Builder deserializeBuilder =
AutoCodecUtil.initializeDeserializeMethodBuilder(encodedType);
AutoCodecUtil.initializeDeserializeMethodBuilder(encodedType, dependency);
buildDeserializeBody(deserializeBuilder, publicFields);
addInstantiatePopulateFieldsAndReturn(deserializeBuilder, encodedType, publicFields);
codecClassBuilder.addMethod(deserializeBuilder.build());
Expand Down Expand Up @@ -446,74 +467,41 @@ private static VariableElement getFieldByName(TypeElement type, String name) {
type.getQualifiedName() + ": no field with name matching " + name));
}

private static TypeSpec.Builder buildClassWithPolymorphicStrategy(TypeElement encodedType) {
private static TypeSpec.Builder buildClassWithPolymorphicStrategy(
TypeElement encodedType, @Nullable TypeElement dependency) {
if (!encodedType.getModifiers().contains(Modifier.ABSTRACT)) {
throw new IllegalArgumentException(
encodedType + " is not abstract, but POLYMORPHIC was selected as the strategy.");
}
TypeSpec.Builder codecClassBuilder = AutoCodecUtil.initializeCodecClassBuilder(encodedType);
TypeSpec.Builder codecClassBuilder =
AutoCodecUtil.initializeCodecClassBuilder(encodedType, dependency);
codecClassBuilder.addMethod(buildPolymorphicSerializeMethod(encodedType));
codecClassBuilder.addMethod(buildPolymorphicDeserializeMethod(encodedType));
codecClassBuilder.addMethod(buildPolymorphicDeserializeMethod(encodedType, dependency));
return codecClassBuilder;
}

private static MethodSpec buildPolymorphicSerializeMethod(TypeElement encodedType) {
MethodSpec.Builder builder = AutoCodecUtil.initializeSerializeMethodBuilder(encodedType);
builder.beginControlFlow("if (input != null)");
builder.addStatement("Class<?> clazz = input.getClass()");
builder.beginControlFlow("try");
builder.addStatement("$T codecField = clazz.getDeclaredField(\"CODEC\")", Field.class);
builder.addStatement("codedOut.writeBoolNoTag(true)");
builder.addStatement(
"$T.asciiOptimized().serialize(clazz.getName(), codedOut)", StringCodecs.class);
builder.addStatement("Object codec = codecField.get(null)");
builder.addStatement(
"$T serializeMethod = codec.getClass().getDeclaredMethod(\"serialize\", clazz, $T.class)",
Method.class,
CodedOutputStream.class);
builder.addStatement("serializeMethod.invoke(codec, input, codedOut)");
builder.nextControlFlow(
"catch ($T|$T|$T|$T e)",
NoSuchFieldException.class,
NoSuchMethodException.class,
IllegalAccessException.class,
InvocationTargetException.class);
builder.addStatement(
"throw new $T(input.getClass().getName(), e)", SerializationException.class);
builder.endControlFlow();
builder.nextControlFlow("else");
builder.addStatement("codedOut.writeBoolNoTag(false)");
builder.endControlFlow();
builder.addStatement("$T.serialize(input, codedOut)", PolymorphicHelper.class);
return builder.build();
}

private static MethodSpec buildPolymorphicDeserializeMethod(TypeElement encodedType) {
MethodSpec.Builder builder = AutoCodecUtil.initializeDeserializeMethodBuilder(encodedType);
builder.addStatement("$T deserialized = null", TypeName.get(encodedType.asType()));
builder.beginControlFlow("if (codedIn.readBool())");
builder.addStatement(
"String className = $T.asciiOptimized().deserialize(codedIn)", StringCodecs.class);
builder.beginControlFlow("try");
builder.addStatement("Class<?> clazz = Class.forName(className)", StringCodecs.class);
builder.addStatement("Object codec = clazz.getDeclaredField(\"CODEC\").get(null)");
builder.addStatement(
"$T deserializeMethod = codec.getClass().getDeclaredMethod(\"deserialize\", $T.class)",
Method.class,
CodedInputStream.class);
builder.addStatement(
"deserialized = ($T)deserializeMethod.invoke(codec, codedIn)",
TypeName.get(encodedType.asType()));
builder.nextControlFlow(
"catch ($T|$T|$T|$T|$T e)",
ClassNotFoundException.class,
NoSuchFieldException.class,
NoSuchMethodException.class,
IllegalAccessException.class,
InvocationTargetException.class);
builder.addStatement("throw new $T(className, e)", SerializationException.class);
builder.endControlFlow();
builder.endControlFlow();
builder.addStatement("return deserialized");
private static MethodSpec buildPolymorphicDeserializeMethod(
TypeElement encodedType, @Nullable TypeElement dependency) {
MethodSpec.Builder builder =
AutoCodecUtil.initializeDeserializeMethodBuilder(encodedType, dependency);
if (dependency == null) {
builder.addStatement(
"return ($T) $T.deserialize(codedIn, null)",
TypeName.get(encodedType.asType()),
PolymorphicHelper.class);
} else {
builder.addStatement(
"return ($T) $T.deserialize(codedIn, $T.ofNullable(dependency))",
TypeName.get(encodedType.asType()),
PolymorphicHelper.class,
Optional.class);
}
return builder.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;

/** Static utilities for AutoCodec processors. */
class AutoCodecUtil {
Expand All @@ -58,7 +57,7 @@ static TypeSpec.Builder initializeCodecClassBuilder(TypeElement encodedType) {
* @param dependency type being injected or null
*/
static TypeSpec.Builder initializeCodecClassBuilder(
TypeElement encodedType, @Nullable VariableElement dependency) {
TypeElement encodedType, @Nullable TypeElement dependency) {
TypeSpec.Builder builder = TypeSpec.classBuilder(getCodecName(encodedType));
if (dependency == null) {
return builder.addSuperinterface(
Expand Down Expand Up @@ -107,7 +106,7 @@ static MethodSpec.Builder initializeDeserializeMethodBuilder(TypeElement encoded
* @param dependency type being injected
*/
static MethodSpec.Builder initializeDeserializeMethodBuilder(
TypeElement encodedType, @Nullable VariableElement dependency) {
TypeElement encodedType, @Nullable TypeElement dependency) {
MethodSpec.Builder builder =
MethodSpec.methodBuilder("deserialize")
.addModifiers(Modifier.PUBLIC)
Expand Down

0 comments on commit d5d508d

Please sign in to comment.