diff options
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) |