aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java
diff options
context:
space:
mode:
authorGravatar Googler <noreply@google.com>2017-12-13 20:15:11 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2017-12-13 20:17:12 -0800
commit1446210debe015946729a71ee2e6992b449bba30 (patch)
tree3fd9d4aa126ba7b728b8317e681519aa2b07c7e5 /src/main/java
parent70b6e5d7df8130f4e622e1ff9c1cd224d208d00e (diff)
Initial @AutoCodec implementation.
PiperOrigin-RevId: 178994972
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/cmdline/Label.java116
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/TestSuiteExpansionValue.java4
-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/autocodec/AutoCodec.java55
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecProcessor.java323
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD44
6 files changed, 491 insertions, 52 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
index 6bb0e5f829..caa5c5ec25 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java
@@ -61,15 +61,16 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
* Package names that aren't made relative to the current repository because they mean special
* things to Bazel.
*/
- public static final ImmutableSet<PathFragment> ABSOLUTE_PACKAGE_NAMES = ImmutableSet.of(
- // Used for select
- PathFragment.create("conditions"),
- // dependencies that are a function of the configuration
- PathFragment.create("tools/defaults"),
- // Visibility is labels aren't actually targets
- PathFragment.create("visibility"),
- // There is only one //external package
- Label.EXTERNAL_PACKAGE_NAME);
+ public static final ImmutableSet<PathFragment> ABSOLUTE_PACKAGE_NAMES =
+ ImmutableSet.of(
+ // Used for select
+ PathFragment.create("conditions"),
+ // dependencies that are a function of the configuration
+ PathFragment.create("tools/defaults"),
+ // Visibility is labels aren't actually targets
+ PathFragment.create("visibility"),
+ // There is only one //external package
+ Label.EXTERNAL_PACKAGE_NAME);
public static final PackageIdentifier EXTERNAL_PACKAGE_IDENTIFIER =
PackageIdentifier.createInMainRepo(EXTERNAL_PACKAGE_NAME);
@@ -78,10 +79,13 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
public static final SkyFunctionName TRANSITIVE_TRAVERSAL =
SkyFunctionName.create("TRANSITIVE_TRAVERSAL");
+ public static final LabelCodec CODEC = LabelCodec.INSTANCE;
+
private static final Interner<Label> LABEL_INTERNER = BlazeInterners.newWeakInterner();
/**
* Factory for Labels from absolute string form. e.g.
+ *
* <pre>
* //foo/bar
* //foo/bar:quux
@@ -98,6 +102,7 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
/**
* Factory for Labels from absolute string form. e.g.
+ *
* <pre>
* //foo/bar
* //foo/bar:quux
@@ -106,8 +111,7 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
* {@literal @}foo//bar:baz
* </pre>
*
- * @param defaultToMain Treat labels in the default repository as being in the main
- * one instead.
+ * @param defaultToMain Treat labels in the default repository as being in the main one instead.
*/
public static Label parseAbsolute(String absName, boolean defaultToMain)
throws LabelSyntaxException {
@@ -156,11 +160,10 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
/**
* Factory for Labels from separate components.
*
- * @param packageName The name of the package. The package name does
- * <b>not</b> include {@code //}. Must be valid according to
- * {@link LabelValidator#validatePackageName}.
- * @param targetName The name of the target within the package. Must be
- * valid according to {@link LabelValidator#validateTargetName}.
+ * @param packageName The name of the package. The package name does <b>not</b> include {@code
+ * //}. Must be valid according to {@link LabelValidator#validatePackageName}.
+ * @param targetName The name of the target within the package. Must be valid according to {@link
+ * LabelValidator#validateTargetName}.
* @throws LabelSyntaxException if either of the arguments was invalid.
*/
public static Label create(String packageName, String targetName) throws LabelSyntaxException {
@@ -168,8 +171,8 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
}
/**
- * Similar factory to above, but takes a package identifier to allow external repository labels
- * to be created.
+ * Similar factory to above, but takes a package identifier to allow external repository labels to
+ * be created.
*/
public static Label create(PackageIdentifier packageId, String targetName)
throws LabelSyntaxException {
@@ -182,7 +185,6 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
* <p>Only call this method if you know what you're doing; in particular, don't call it on
* arbitrary {@code targetName} inputs
*/
-
public static Label createUnvalidated(PackageIdentifier packageId, String targetName) {
return LABEL_INTERNER.intern(new Label(packageId, StringCanonicalizer.intern(targetName)));
}
@@ -190,6 +192,7 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
/**
* Resolves a relative label using a workspace-relative path to the current working directory. The
* method handles these cases:
+ *
* <ul>
* <li>The label is absolute.
* <li>The label starts with a colon.
@@ -246,8 +249,8 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
}
/**
- * Validates the given package name and returns a canonical {@link PackageIdentifier} instance
- * if it is valid. Otherwise it throws a SyntaxException.
+ * Validates the given package name and returns a canonical {@link PackageIdentifier} instance if
+ * it is valid. Otherwise it throws a SyntaxException.
*/
private static PackageIdentifier validatePackageName(String packageIdentifier, String name)
throws LabelSyntaxException {
@@ -285,9 +288,7 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
this.hashCode = hashCode(this.name, this.packageIdentifier);
}
- /**
- * A specialization of Arrays.HashCode() that does not require constructing a 2-element array.
- */
+ /** A specialization of Arrays.HashCode() that does not require constructing a 2-element array. */
private static final int hashCode(Object obj1, Object obj2) {
int result = 31 + (obj1 == null ? 0 : obj1.hashCode());
return 31 * result + (obj2 == null ? 0 : obj2.hashCode());
@@ -309,10 +310,14 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
* Returns the name of the package in which this rule was declared (e.g. {@code
* //file/base:fileutils_test} returns {@code file/base}).
*/
- @SkylarkCallable(name = "package", structField = true,
- doc = "The package part of this label. "
- + "For instance:<br>"
- + "<pre class=language-python>Label(\"//pkg/foo:abc\").package == \"pkg/foo\"</pre>")
+ @SkylarkCallable(
+ name = "package",
+ structField = true,
+ doc =
+ "The package part of this label. "
+ + "For instance:<br>"
+ + "<pre class=language-python>Label(\"//pkg/foo:abc\").package == \"pkg/foo\"</pre>"
+ )
public String getPackageName() {
return packageIdentifier.getPackageFragment().getPathString();
}
@@ -322,11 +327,15 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
* {@code @repo//pkg:b}, it will returns {@code external/repo/pkg} and for label {@code //pkg:a},
* it will returns an empty string.
*/
- @SkylarkCallable(name = "workspace_root", structField = true,
- doc = "Returns the execution root for the workspace of this label, relative to the execroot. "
- + "For instance:<br>"
- + "<pre class=language-python>Label(\"@repo//pkg/foo:abc\").workspace_root =="
- + " \"external/repo\"</pre>")
+ @SkylarkCallable(
+ name = "workspace_root",
+ structField = true,
+ doc =
+ "Returns the execution root for the workspace of this label, relative to the execroot. "
+ + "For instance:<br>"
+ + "<pre class=language-python>Label(\"@repo//pkg/foo:abc\").workspace_root =="
+ + " \"external/repo\"</pre>"
+ )
public String getWorkspaceRoot() {
return packageIdentifier.getRepository().getSourceRoot().toString();
}
@@ -343,21 +352,23 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
return packageIdentifier.getPackageFragment();
}
- /**
- * Returns the label as a path fragment, using the package and the label name.
- */
+ /** Returns the label as a path fragment, using the package and the label name. */
public PathFragment toPathFragment() {
return packageIdentifier.getPackageFragment().getRelative(name);
}
/**
- * Returns the name by which this rule was declared (e.g. {@code //foo/bar:baz}
- * returns {@code baz}).
+ * Returns the name by which this rule was declared (e.g. {@code //foo/bar:baz} returns {@code
+ * baz}).
*/
- @SkylarkCallable(name = "name", structField = true,
- doc = "The name of this label within the package. "
- + "For instance:<br>"
- + "<pre class=language-python>Label(\"//pkg/foo:abc\").name == \"abc\"</pre>")
+ @SkylarkCallable(
+ name = "name",
+ structField = true,
+ doc =
+ "The name of this label within the package. "
+ + "For instance:<br>"
+ + "<pre class=language-python>Label(\"//pkg/foo:abc\").name == \"abc\"</pre>"
+ )
public String getName() {
return name;
}
@@ -382,13 +393,16 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
}
public String getUnambiguousCanonicalForm() {
- return packageIdentifier.getRepository() + "//" + packageIdentifier.getPackageFragment()
- + ":" + name;
+ return packageIdentifier.getRepository()
+ + "//"
+ + packageIdentifier.getPackageFragment()
+ + ":"
+ + name;
}
/**
- * Renders this label in canonical form, except with labels in the main and default
- * repositories conflated.
+ * Renders this label in canonical form, except with labels in the main and default repositories
+ * conflated.
*/
public String getDefaultCanonicalForm() {
String repository;
@@ -397,8 +411,7 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
} else {
repository = packageIdentifier.getRepository().getName();
}
- return repository + "//" + packageIdentifier.getPackageFragment()
- + ":" + name;
+ return repository + "//" + packageIdentifier.getPackageFragment() + ":" + name;
}
/**
@@ -517,9 +530,7 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
return hashCode;
}
- /**
- * Two labels are equal iff both their name and their package name are equal.
- */
+ /** Two labels are equal iff both their name and their package name are equal. */
@Override
public boolean equals(Object other) {
if (!(other instanceof Label)) {
@@ -527,7 +538,8 @@ public final class Label implements Comparable<Label>, Serializable, SkylarkValu
}
Label otherLabel = (Label) other;
// Perform the equality comparisons in order from least likely to most likely.
- return hashCode == otherLabel.hashCode && name.equals(otherLabel.name)
+ return hashCode == otherLabel.hashCode
+ && name.equals(otherLabel.name)
&& packageIdentifier.equals(otherLabel.packageIdentifier);
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TestSuiteExpansionValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TestSuiteExpansionValue.java
index c03e09184f..10291a6798 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TestSuiteExpansionValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TestSuiteExpansionValue.java
@@ -23,6 +23,7 @@ import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.skyframe.serialization.NotSerializableRuntimeException;
+import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
@@ -76,6 +77,9 @@ public final class TestSuiteExpansionValue implements SkyValue {
/** A list of targets of which all test suites should be expanded. */
@ThreadSafe
static final class TestSuiteExpansionKey implements SkyKey {
+ public static final ObjectCodec<TestSuiteExpansionKey> CODEC =
+ TestSuiteExpansionKeyCodec.INSTANCE;
+
private final ImmutableSortedSet<Label> targets;
public TestSuiteExpansionKey(ImmutableSortedSet<Label> targets) {
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 bb25cbcffd..af255e7b40 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
@@ -3,6 +3,7 @@ package(default_visibility = ["//src:__subpackages__"])
filegroup(
name = "srcs",
srcs = glob(["**"]) + [
+ "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec:srcs",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils:srcs",
],
)
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
new file mode 100644
index 0000000000..17e3224fbf
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodec.java
@@ -0,0 +1,55 @@
+// Copyright 2017 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.autocodec;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies that AutoCodec should generate a codec implementation for the annotated abstract class.
+ *
+ * <p>Example:
+ *
+ * <pre>{@code
+ * @AutoCodec
+ * abstract class Codec implements ObjectCodec<Target> {
+ * static Codec create() {
+ * return new AutoCodec_Target();
+ * }
+ * }
+ * }</pre>
+ *
+ * The {@code AutoCodec_} prefix is added to the {@Target} to obtain the generated class name.
+ */
+@Target(ElementType.TYPE)
+public @interface AutoCodec {
+ /**
+ * AutoCodec recursively derives a codec using the public interfaces of the class.
+ *
+ * <p>Specific strategies are described below.
+ */
+ public static enum Strategy {
+ /**
+ * Uses the constructor to infer serialization code.
+ *
+ * <p>Each constructor parameter is expected to have a corresponding getter. These pairs are
+ * used for serialization and deserialization.
+ */
+ CONSTRUCTOR,
+ // TODO(shahan): Add a strategy that serializes from public members.
+ }
+
+ Strategy strategy() default Strategy.CONSTRUCTOR;
+}
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
new file mode 100644
index 0000000000..29df3cb46e
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodecProcessor.java
@@ -0,0 +1,323 @@
+// Copyright 2017 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.autocodec;
+
+import com.google.auto.service.AutoService;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
+import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+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.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.Diagnostic;
+
+/**
+ * Javac annotation processor (compiler plugin) for generating {@link ObjectCodec} implementations.
+ *
+ * <p>User code must never reference this class.
+ */
+@AutoService(Processor.class)
+public class AutoCodecProcessor extends AbstractProcessor {
+ // Synthesized classes will be prefixed with AutoCodec_.
+ public static final String GENERATED_CLASS_NAME_PREFIX = "AutoCodec";
+ private static final Class<AutoCodec> ANNOTATION = AutoCodec.class;
+
+ /**
+ * Passing {@code --javacopt=-Aautocodec_print_generated} to {@code blaze build} tells AutoCodec
+ * to print the generated code.
+ */
+ private static final String PRINT_GENERATED_OPTION = "autocodec_print_generated";
+
+ private ProcessingEnvironment env; // Captured from `init` method.
+
+ @Override
+ public Set<String> getSupportedOptions() {
+ return ImmutableSet.of(PRINT_GENERATED_OPTION);
+ }
+
+ @Override
+ public Set<String> getSupportedAnnotationTypes() {
+ return ImmutableSet.of(ANNOTATION.getCanonicalName());
+ }
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latestSupported(); // Supports all versions of Java.
+ }
+
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+ this.env = processingEnv;
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ for (Element element : roundEnv.getElementsAnnotatedWith(ANNOTATION)) {
+ AutoCodec annotation = element.getAnnotation(ANNOTATION);
+ switch (annotation.strategy()) {
+ case CONSTRUCTOR:
+ buildCodecUsingConstructor((TypeElement) element);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown strategy: " + annotation.strategy());
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Uses the first constructor of the class to synthesize a codec.
+ *
+ * <p>This strategy depends on
+ *
+ * <ul>
+ * <li>the class constructor taking all serialized fields as parameters
+ * <li>and each serialized field having a corresponding getter.
+ * </ul>
+ *
+ * For example, a constructor having parameter, {@code target}, should having a matching getter,
+ * {@code getTarget()}.
+ *
+ * <p>The first constructor is the first ocurring in the source code.
+ */
+ private void buildCodecUsingConstructor(TypeElement classElement) {
+ TypeSpec.Builder codecClassBuilder =
+ TypeSpec.classBuilder(getCodecName(classElement))
+ .superclass(TypeName.get(classElement.asType()));
+
+ TypeElement encodedType = getEncodedType(classElement);
+
+ // Generates the getEncodedClass method.
+ codecClassBuilder.addMethod(
+ MethodSpec.methodBuilder("getEncodedClass")
+ .addModifiers(Modifier.PUBLIC)
+ .addAnnotation(Override.class)
+ .returns(
+ ParameterizedTypeName.get(
+ ClassName.get(Class.class), TypeName.get(encodedType.asType())))
+ .addStatement("return $T.class", TypeName.get(encodedType.asType()))
+ .build());
+
+ // In Java, every class has a constructor, so this always succeeds.
+ ExecutableElement constructor =
+ ElementFilter.constructorsIn(encodedType.getEnclosedElements()).get(0);
+ List<? extends VariableElement> constructorParameters = constructor.getParameters();
+ addSerializeMethodUsingConstructor(codecClassBuilder, encodedType, constructorParameters);
+ addDeserializeMethodUsingConstructor(codecClassBuilder, encodedType, constructorParameters);
+
+ String packageName =
+ env.getElementUtils().getPackageOf(classElement).getQualifiedName().toString();
+ try {
+ JavaFile file = JavaFile.builder(packageName, codecClassBuilder.build()).build();
+ file.writeTo(env.getFiler());
+ if (env.getOptions().containsKey("autocodec_print_generated")) {
+ note("AutoCodec generated codec for " + classElement + ":\n" + file);
+ }
+ } catch (IOException e) {
+ env.getMessager()
+ .printMessage(Diagnostic.Kind.ERROR, "Failed to generate output file: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Heuristic that converts a constructor parameter to a getter.
+ *
+ * <p>For example, a parameter called {@code target} results in {@code getTarget()}.
+ */
+ private static String paramNameAsAccessor(String name) {
+ return "get" + name.substring(0, 1).toUpperCase() + name.substring(1) + "()";
+ }
+
+ /**
+ * Name of the generated codec class.
+ *
+ * <p>For {@code Foo.Bar.Codec} this is {@code AutoCodec_Foo_Bar_Codec}.
+ */
+ private static String getCodecName(Element element) {
+ ImmutableList.Builder<String> classNamesBuilder = new ImmutableList.Builder<>();
+ do {
+ classNamesBuilder.add(element.getSimpleName().toString());
+ element = element.getEnclosingElement();
+ } while (element instanceof TypeElement);
+ classNamesBuilder.add(GENERATED_CLASS_NAME_PREFIX);
+ return classNamesBuilder.build().reverse().stream().collect(Collectors.joining("_"));
+ }
+
+ private void addSerializeMethodUsingConstructor(
+ TypeSpec.Builder codecClassBuilder,
+ TypeElement encodedType,
+ List<? extends VariableElement> constructorParameters) {
+ MethodSpec.Builder serializeBuilder =
+ MethodSpec.methodBuilder("serialize")
+ .addModifiers(Modifier.PUBLIC)
+ .returns(void.class)
+ .addParameter(TypeName.get(encodedType.asType()), "input")
+ .addParameter(CodedOutputStream.class, "codedOut")
+ .addAnnotation(Override.class)
+ .addException(SerializationException.class)
+ .addException(IOException.class);
+ for (VariableElement parameter : constructorParameters) {
+ buildSerializeBody(
+ serializeBuilder,
+ (DeclaredType) parameter.asType(),
+ "input." + paramNameAsAccessor(parameter.getSimpleName().toString()));
+ }
+ codecClassBuilder.addMethod(serializeBuilder.build());
+ }
+
+ private void addDeserializeMethodUsingConstructor(
+ TypeSpec.Builder codecClassBuilder,
+ TypeElement encodedType,
+ List<? extends VariableElement> constructorParameters) {
+ MethodSpec.Builder deserializeBuilder =
+ MethodSpec.methodBuilder("deserialize")
+ .addModifiers(Modifier.PUBLIC)
+ .returns(TypeName.get(encodedType.asType()))
+ .addParameter(CodedInputStream.class, "codedIn")
+ .addAnnotation(Override.class)
+ .addException(SerializationException.class)
+ .addException(IOException.class);
+ for (VariableElement parameter : constructorParameters) {
+ buildDeserializeBody(
+ deserializeBuilder,
+ (DeclaredType) parameter.asType(),
+ parameter.getSimpleName().toString());
+ }
+ // Invokes the constructor and returns the value.
+ deserializeBuilder.addStatement(
+ "return new $T($L)",
+ TypeName.get(encodedType.asType()),
+ constructorParameters
+ .stream()
+ .map(p -> p.getSimpleName().toString())
+ .collect(Collectors.joining(", ")));
+ codecClassBuilder.addMethod(deserializeBuilder.build());
+ }
+
+ /**
+ * Appends code statements to {@code builder} to serialize a pre-declared variable named {@code
+ * accessor}.
+ *
+ * @param type the type of {@code accessor}
+ */
+ private void buildSerializeBody(MethodSpec.Builder builder, DeclaredType type, String accessor) {
+ builder.beginControlFlow("if ($L != null)", accessor); // Begin if not null block.
+ builder.addStatement("codedOut.writeBoolNoTag(true)");
+ // TODO(shahan): Add support for more types.
+ if (matchesErased(type, ImmutableSortedSet.class)) {
+ // Writes the target count to the stream so deserialization knows when to stop.
+ builder.addStatement("codedOut.writeInt32NoTag($L.size())", accessor);
+ DeclaredType repeatedType = (DeclaredType) type.getTypeArguments().get(0);
+ // TODO(shahan): consider introducing a depth parameter to avoid shadowing here.
+ builder.beginControlFlow("for ($T repeated : $L)", TypeName.get(repeatedType), accessor);
+ buildSerializeBody(builder, repeatedType, "repeated");
+ builder.endControlFlow();
+ } else {
+ // Otherwise use the type's CODEC.
+ builder.addStatement("$T.CODEC.serialize($L, codedOut)", TypeName.get(type), accessor);
+ }
+ builder.nextControlFlow("else");
+ builder.addStatement("codedOut.writeBoolNoTag(false)");
+ builder.endControlFlow(); // End if not null.
+ }
+
+ /**
+ * Appends code statements to {@code builder} declaring a variable called {@code name} and
+ * initializing it by deserialization.
+ *
+ * @param type the type of {@code name}
+ */
+ private void buildDeserializeBody(MethodSpec.Builder builder, DeclaredType type, String name) {
+ builder.addStatement("$T $L = null", TypeName.get(type), name);
+ builder.beginControlFlow("if (codedIn.readBool())"); // Begin null-handling block.
+ // TODO(shahan): Add support for more types.
+ if (matchesErased(type, ImmutableSortedSet.class)) {
+ DeclaredType repeatedType = (DeclaredType) type.getTypeArguments().get(0);
+ builder.addStatement(
+ "$T<$T> builder = new $T<>($T.naturalOrder())",
+ ImmutableSortedSet.Builder.class,
+ TypeName.get(repeatedType),
+ ImmutableSortedSet.Builder.class,
+ Comparator.class);
+ builder.addStatement("int length = codedIn.readInt32()");
+ builder.beginControlFlow("for (int i = 0; i < length; ++i)");
+ buildDeserializeBody(builder, repeatedType, "repeated");
+ builder.addStatement("builder.add(repeated)");
+ builder.endControlFlow();
+ builder.addStatement("$L = builder.build()", name);
+ } else {
+ // Otherwise, use the type's CODEC value.
+ builder.addStatement("$L = $T.CODEC.deserialize(codedIn)", name, TypeName.get(type));
+ }
+ builder.endControlFlow(); // End null-handling block.
+ }
+
+ /**
+ * Gets the type parameter of ObjectCodec, i.e., the type being encoded.
+ *
+ * <p>{@code element} must implement ObjectCodec.
+ */
+ private TypeElement getEncodedType(TypeElement element) {
+ for (TypeMirror implementedInterface : element.getInterfaces()) {
+ if (matchesErased(implementedInterface, ObjectCodec.class)) {
+ return (TypeElement)
+ env.getTypeUtils()
+ .asElement(((DeclaredType) implementedInterface).getTypeArguments().get(0));
+ }
+ }
+ throw new IllegalArgumentException(element + " does not implement ObjectCodec!");
+ }
+
+ /** True when erasure of {@code type} matches erasure of {@code clazz}. */
+ private boolean matchesErased(TypeMirror type, Class<?> clazz) {
+ return env.getTypeUtils()
+ .isSameType(
+ env.getTypeUtils().erasure(type),
+ env.getTypeUtils()
+ .erasure(
+ env.getElementUtils().getTypeElement((clazz.getCanonicalName())).asType()));
+ }
+
+ /** Emits a note to BUILD log during annotation processing for debugging. */
+ private void note(String note) {
+ env.getMessager().printMessage(Diagnostic.Kind.NOTE, note);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD
new file mode 100644
index 0000000000..5807597e65
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD
@@ -0,0 +1,44 @@
+package(default_visibility = ["//src:__subpackages__"])
+
+filegroup(
+ name = "srcs",
+ srcs = glob(
+ ["**"],
+ ),
+)
+
+# @AutoCodec annotation and plugin. Used by clients.
+java_library(
+ name = "autocodec",
+ exported_plugins = [":autocodec-plugin"],
+ exports = [":autocodec-annotation"],
+)
+
+# @AutoCodec annotation only. Used by clients and the processor.
+java_library(
+ name = "autocodec-annotation",
+ srcs = ["AutoCodec.java"],
+)
+
+# Installs the @AutoCodec annotation processor as a compiler plugin.
+java_plugin(
+ name = "autocodec-plugin",
+ processor_class = "com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodecProcessor",
+ deps = [
+ ":autocodec-processor",
+ ],
+)
+
+# @AutoCodec annotation processor implementation.
+java_library(
+ name = "autocodec-processor",
+ srcs = ["AutoCodecProcessor.java"],
+ deps = [
+ ":autocodec-annotation",
+ "//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
+ "//third_party:auto_service",
+ "//third_party:guava",
+ "//third_party/java/javapoet",
+ "//third_party/protobuf:protobuf_java",
+ ],
+)