aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD1
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/serialization/PolymorphicHelper.java97
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodec.java15
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecProcessor.java124
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecUtil.java5
5 files changed, 169 insertions, 73 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD
index af255e7b40..5fd16ef4cc 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/BUILD
@@ -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",
],
)
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/PolymorphicHelper.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/PolymorphicHelper.java
new file mode 100644
index 0000000000..10af13ea21
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/PolymorphicHelper.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodec.java
index 638b18897a..3f3319f278 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodec.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodec.java
@@ -79,8 +79,7 @@ public @interface AutoCodec {
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
@@ -99,4 +98,16 @@ public @interface AutoCodec {
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;
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecProcessor.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecProcessor.java
index 0c4d34bef3..af07ff529e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecProcessor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecProcessor.java
@@ -20,20 +20,16 @@ import com.google.auto.service.AutoService;
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;
@@ -48,6 +44,7 @@ import javax.lang.model.element.Modifier;
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;
@@ -96,16 +93,23 @@ public class AutoCodecProcessor extends AbstractProcessor {
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());
@@ -131,6 +135,21 @@ public class AutoCodecProcessor extends AbstractProcessor {
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);
@@ -160,7 +179,7 @@ public class AutoCodecProcessor extends AbstractProcessor {
*
* <p>Null if no such parameter exists.
*/
- @Nullable VariableElement dependency;
+ @Nullable TypeElement dependency;
}
/** Separates any dependency from the constructor parameters. */
@@ -181,7 +200,7 @@ public class AutoCodecProcessor extends AbstractProcessor {
+ " constructor has multiple Dependency annotations.");
}
if (!dependencies.isEmpty()) {
- result.dependency = dependencies.get(0);
+ result.dependency = (TypeElement) ((DeclaredType) dependencies.get(0).asType()).asElement();
}
return result;
}
@@ -253,8 +272,10 @@ public class AutoCodecProcessor extends AbstractProcessor {
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()
@@ -262,7 +283,7 @@ public class AutoCodecProcessor extends AbstractProcessor {
.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());
@@ -446,74 +467,41 @@ public class AutoCodecProcessor extends AbstractProcessor {
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();
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecUtil.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecUtil.java
index e958c3b60b..2f20815de3 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecUtil.java
@@ -31,7 +31,6 @@ import javax.annotation.Nullable;
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 {
@@ -58,7 +57,7 @@ class AutoCodecUtil {
* @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(
@@ -107,7 +106,7 @@ class AutoCodecUtil {
* @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)