blob: d027dfa74e629dc0f60b252012a0b8cd09693749 [file] [log] [blame]
// Copyright 2023 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 static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.getErasure;
import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.getSuperclass;
import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.isSerializableField;
import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.matchesType;
import static javax.lang.model.util.ElementFilter.fieldsIn;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.skyframe.serialization.AsyncDeserializationContext;
import com.google.devtools.build.lib.skyframe.serialization.InterningObjectCodec;
import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
import com.google.devtools.build.lib.unsafe.UnsafeProvider;
import com.google.protobuf.CodedInputStream;
import com.squareup.javapoet.ClassName;
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.ArrayList;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
/** Generates instances of {@link InterningObjectCodec}. */
final class InterningObjectCodecGenerator extends CodecGenerator {
InterningObjectCodecGenerator(ProcessingEnvironment env) {
super(env);
}
@Override
ImmutableList<FieldGenerator> getFieldGenerators(TypeElement type)
throws SerializationProcessingException {
// Collects the type and its supertypes.
ArrayList<TypeElement> types = new ArrayList<>();
for (TypeElement next = type;
next != null && !matchesType(next.asType(), Object.class, env);
next = getSuperclass(next)) {
types.add(next);
}
ImmutableList.Builder<FieldGenerator> result = ImmutableList.builder();
// Iterates in reverse order so variables are ordered highest superclass first, as they would
// be ordered in the class layout.
for (int i = types.size() - 1; i >= 0; i--) {
for (VariableElement variable : fieldsIn(types.get(i).getEnclosedElements())) {
if (!isSerializableField(variable)) {
continue;
}
result.add(InterningObjectCodecFieldGenerators.create(variable, i, env));
}
}
return result.build();
}
@Override
void performAdditionalCodecInitialization(
TypeSpec.Builder classBuilder, TypeElement encodedType, ExecutableElement internMethod) {
TypeName returnType = getErasure(encodedType, env);
classBuilder.superclass(
ParameterizedTypeName.get(ClassName.get(InterningObjectCodec.class), returnType));
// Defines the `InterningObjectCodec.intern` implementation.
VariableElement param = getOnlyElement(internMethod.getParameters());
classBuilder.addMethod(
MethodSpec.methodBuilder("intern")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(getErasure(param.asType(), env), "value")
.returns(returnType)
.addStatement(
"return $T.$L(value)", getErasure(encodedType, env), internMethod.getSimpleName())
.build());
}
@Override
MethodSpec.Builder initializeConstructor(boolean hasFields) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder();
if (hasFields) {
constructor.beginControlFlow("try");
}
return constructor;
}
/** Initializes the {@link InterningObjectCodec#deserializeInterned} method. */
@Override
MethodSpec.Builder initializeDeserializeMethod(TypeElement encodedType) {
TypeName typeName = getErasure(encodedType, env);
return MethodSpec.methodBuilder("deserializeInterned")
.addModifiers(Modifier.PUBLIC)
.returns(typeName)
.addAnnotation(Override.class)
.addException(SerializationException.class)
.addException(IOException.class)
.addParameter(AsyncDeserializationContext.class, "context")
.addParameter(CodedInputStream.class, "codedIn")
.addStatement("$T instance", typeName)
.beginControlFlow("try")
.addStatement(
"instance = ($T) $T.unsafe().allocateInstance($T.class)",
typeName,
UnsafeProvider.class,
typeName)
.nextControlFlow("catch ($T e)", ReflectiveOperationException.class)
.addStatement("throw new $T(e)", IllegalStateException.class)
.endControlFlow();
}
@Override
void addImplementationToEndOfMethods(
ExecutableElement unusedInterner,
MethodSpec.Builder constructor,
MethodSpec.Builder deserialize,
boolean hasFields) {
if (hasFields) {
constructor
.nextControlFlow("catch ($T e)", NoSuchFieldException.class)
.addStatement("throw new $T(e)", AssertionError.class)
.endControlFlow();
}
deserialize.addStatement("return instance");
}
}