| // 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 static com.google.common.base.Ascii.toLowerCase; |
| import static com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodecProcessor.InstantiatorKind.CONSTRUCTOR; |
| import static com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodecProcessor.InstantiatorKind.FACTORY_METHOD; |
| import static com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodecProcessor.InstantiatorKind.INTERNER; |
| import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.getErasure; |
| import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.getErasureAsMirror; |
| import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.sanitizeTypeParameter; |
| import static com.google.devtools.build.lib.skyframe.serialization.autocodec.TypeOperations.writeGeneratedClassToFile; |
| |
| import com.google.auto.service.AutoService; |
| import com.google.auto.value.AutoValue; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec; |
| import com.google.devtools.build.lib.skyframe.serialization.SerializationException; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.Instantiator; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.Interner; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationCodeGenerator.Marshaller; |
| import com.google.devtools.build.lib.unsafe.UnsafeProvider; |
| 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.util.List; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| import javax.annotation.Nullable; |
| 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.ElementKind; |
| 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.TypeKind; |
| 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 { |
| /** |
| * 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. |
| private Marshallers marshallers; |
| |
| @Override |
| public Set<String> getSupportedOptions() { |
| return ImmutableSet.of(PRINT_GENERATED_OPTION); |
| } |
| |
| @Override |
| public Set<String> getSupportedAnnotationTypes() { |
| return ImmutableSet.of(AutoCodec.class.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; |
| this.marshallers = new Marshallers(processingEnv); |
| } |
| |
| @Override |
| public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { |
| try { |
| processInternal(roundEnv); |
| } catch (SerializationProcessingException e) { |
| // Reporting a message with ERROR kind will fail compilation. |
| env.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), e.getElement()); |
| } |
| return false; |
| } |
| |
| private void processInternal(RoundEnvironment roundEnv) throws SerializationProcessingException { |
| for (Element element : roundEnv.getElementsAnnotatedWith(AutoCodec.class)) { |
| AutoCodec annotation = element.getAnnotation(AutoCodec.class); |
| TypeElement encodedType = (TypeElement) element; |
| ResolvedInstantiator instantiator = determineInstantiator(encodedType); |
| TypeSpec codecClass; |
| switch (instantiator.kind()) { |
| case CONSTRUCTOR: |
| case FACTORY_METHOD: |
| codecClass = defineClassWithInstantiator(encodedType, instantiator.method(), annotation); |
| break; |
| case INTERNER: |
| codecClass = defineClassWithInterner(encodedType, instantiator.method(), annotation); |
| break; |
| default: |
| throw new IllegalStateException( |
| String.format("Unknown instantiator kind: %s\n", instantiator)); |
| } |
| |
| JavaFile file = writeGeneratedClassToFile(element, codecClass, env); |
| if (env.getOptions().containsKey(PRINT_GENERATED_OPTION)) { |
| note("AutoCodec generated codec for " + element + ":\n" + file); |
| } |
| } |
| } |
| |
| private TypeSpec defineClassWithInstantiator( |
| TypeElement encodedType, ExecutableElement instantiator, AutoCodec annotation) |
| throws SerializationProcessingException { |
| List<? extends VariableElement> fields = instantiator.getParameters(); |
| |
| TypeSpec.Builder codecClassBuilder = |
| Initializers.initializeCodecClassBuilder(encodedType, env) |
| .addSuperinterface( |
| ParameterizedTypeName.get( |
| ClassName.get(ObjectCodec.class), getErasure(encodedType, env))); |
| |
| if (encodedType.getAnnotation(AutoValue.class) == null) { |
| initializeUnsafeOffsets(codecClassBuilder, encodedType, fields); |
| codecClassBuilder.addMethod( |
| buildSerializeMethodWithInstantiator(encodedType, fields, annotation)); |
| } else { |
| codecClassBuilder.addMethod( |
| buildSerializeMethodWithInstantiatorForAutoValue(encodedType, fields, annotation)); |
| } |
| |
| MethodSpec.Builder deserializeBuilder = |
| Initializers.initializeDeserializeMethodBuilder(encodedType, env); |
| buildDeserializeBody(deserializeBuilder, fields); |
| addReturnNew(deserializeBuilder, encodedType, instantiator, env); |
| codecClassBuilder.addMethod(deserializeBuilder.build()); |
| |
| return codecClassBuilder.build(); |
| } |
| |
| private TypeSpec defineClassWithInterner( |
| TypeElement encodedType, ExecutableElement interner, AutoCodec annotation) |
| throws SerializationProcessingException { |
| return new InterningObjectCodecGenerator(env).defineCodec(encodedType, annotation, interner); |
| } |
| |
| enum InstantiatorKind { |
| CONSTRUCTOR, |
| FACTORY_METHOD, |
| INTERNER |
| } |
| |
| @AutoValue |
| abstract static class ResolvedInstantiator { |
| public abstract InstantiatorKind kind(); |
| |
| public abstract ExecutableElement method(); |
| |
| private static ResolvedInstantiator create(InstantiatorKind kind, ExecutableElement method) { |
| return new AutoValue_AutoCodecProcessor_ResolvedInstantiator(kind, method); |
| } |
| } |
| |
| /** |
| * Determines the {@link ResolvedInstantiator} by scanning the constructors and methods for |
| * annotations. |
| * |
| * <p>Identifies the {@link Instantiator} or {@link Interner} annotations, throwing an exception |
| * if there are multiple. Falls back to checking for a unique constructor or throws otherwise. |
| */ |
| private ResolvedInstantiator determineInstantiator(TypeElement encodedType) |
| throws SerializationProcessingException { |
| InstantiatorKind instantiatorKind = null; |
| ExecutableElement markedMethod = null; |
| |
| List<ExecutableElement> constructors = |
| ElementFilter.constructorsIn(encodedType.getEnclosedElements()); |
| |
| for (ExecutableElement constructor : constructors) { |
| if (hasInstantiatorAnnotation(constructor)) { |
| if (markedMethod != null) { |
| throw new SerializationProcessingException( |
| encodedType, |
| "%s has multiple constructors with the Instantiator annotation.", |
| encodedType.getQualifiedName()); |
| } |
| markedMethod = constructor; |
| instantiatorKind = CONSTRUCTOR; |
| } |
| } |
| |
| for (ExecutableElement method : ElementFilter.methodsIn(encodedType.getEnclosedElements())) { |
| if (hasInstantiatorAnnotation(method)) { |
| verifyFactoryMethod(encodedType, method); |
| if (markedMethod != null) { |
| throw new SerializationProcessingException( |
| encodedType, |
| "%s has multiple Instantiator or Interner annotations: %s %s.", |
| encodedType.getQualifiedName(), |
| markedMethod.getSimpleName(), |
| method.getSimpleName()); |
| } |
| markedMethod = method; |
| instantiatorKind = FACTORY_METHOD; |
| } |
| if (hasInternerAnnotation(method)) { |
| verifyInterner(encodedType, method); |
| if (markedMethod != null) { |
| throw new SerializationProcessingException( |
| encodedType, |
| "%s has multiple Instantiator or Interner annotations: %s %s.", |
| encodedType.getQualifiedName(), |
| markedMethod.getSimpleName(), |
| method.getSimpleName()); |
| } |
| markedMethod = method; |
| instantiatorKind = INTERNER; |
| } |
| } |
| |
| if (markedMethod != null) { |
| return ResolvedInstantiator.create(instantiatorKind, markedMethod); |
| } |
| |
| // If nothing is marked, see if there is a unique constructor. |
| if (constructors.size() > 1) { |
| throw new SerializationProcessingException( |
| encodedType, |
| "%s has multiple constructors but no Instantiator or Interner annotation.", |
| encodedType.getQualifiedName()); |
| } |
| // In Java, every class has at least one constructor, so this never fails. |
| return ResolvedInstantiator.create(CONSTRUCTOR, constructors.get(0)); |
| } |
| |
| private static boolean hasInstantiatorAnnotation(ExecutableElement elt) { |
| return elt.getAnnotation(Instantiator.class) != null; |
| } |
| |
| private static boolean hasInternerAnnotation(ExecutableElement elt) { |
| return elt.getAnnotation(Interner.class) != null; |
| } |
| |
| private enum Relation { |
| INSTANCE_OF, |
| EQUAL_TO, |
| SUPERTYPE_OF, |
| UNRELATED_TO |
| } |
| |
| @Nullable |
| private Relation findRelationWithGenerics(TypeMirror type1, TypeMirror type2) { |
| if (type1.getKind() == TypeKind.TYPEVAR |
| || type1.getKind() == TypeKind.WILDCARD |
| || type2.getKind() == TypeKind.TYPEVAR |
| || type2.getKind() == TypeKind.WILDCARD) { |
| return Relation.EQUAL_TO; |
| } |
| if (env.getTypeUtils().isAssignable(type1, type2)) { |
| if (env.getTypeUtils().isAssignable(type2, type1)) { |
| return Relation.EQUAL_TO; |
| } |
| return Relation.INSTANCE_OF; |
| } |
| if (env.getTypeUtils().isAssignable(type2, type1)) { |
| return Relation.SUPERTYPE_OF; |
| } |
| // From here on out, we can't detect subtype/supertype, we're only checking for equality. |
| TypeMirror erasedType1 = env.getTypeUtils().erasure(type1); |
| TypeMirror erasedType2 = env.getTypeUtils().erasure(type2); |
| if (!env.getTypeUtils().isSameType(erasedType1, erasedType2)) { |
| // Technically, there could be a relationship, but it's too hard to figure out for now. |
| return Relation.UNRELATED_TO; |
| } |
| List<? extends TypeMirror> genericTypes1 = ((DeclaredType) type1).getTypeArguments(); |
| List<? extends TypeMirror> genericTypes2 = ((DeclaredType) type2).getTypeArguments(); |
| if (genericTypes1.size() != genericTypes2.size()) { |
| return null; |
| } |
| for (int i = 0; i < genericTypes1.size(); i++) { |
| Relation result = findRelationWithGenerics(genericTypes1.get(i), genericTypes2.get(i)); |
| if (result != Relation.EQUAL_TO) { |
| return Relation.UNRELATED_TO; |
| } |
| } |
| return Relation.EQUAL_TO; |
| } |
| |
| private void verifyFactoryMethod(TypeElement encodedType, ExecutableElement elt) |
| throws SerializationProcessingException { |
| boolean success = elt.getModifiers().contains(Modifier.STATIC); |
| if (success) { |
| Relation equalityTest = findRelationWithGenerics(elt.getReturnType(), encodedType.asType()); |
| success = equalityTest == Relation.EQUAL_TO || equalityTest == Relation.INSTANCE_OF; |
| } |
| if (!success) { |
| throw new SerializationProcessingException( |
| encodedType, |
| "%s tags %s as an Instantiator, but it's not a valid factory method %s, %s", |
| encodedType, |
| elt.getSimpleName(), |
| elt.getReturnType(), |
| encodedType.asType()); |
| } |
| } |
| |
| private void verifyInterner(TypeElement encodedType, ExecutableElement method) |
| throws SerializationProcessingException { |
| if (!method.getModifiers().contains(Modifier.STATIC)) { |
| throw new SerializationProcessingException( |
| encodedType, "%s is tagged @Interner, but it's not static.", method.getSimpleName()); |
| } |
| List<? extends VariableElement> parameters = method.getParameters(); |
| if (parameters.size() != 1) { |
| throw new SerializationProcessingException( |
| encodedType, |
| "%s is tagged @Interner, but it has %d parameters instead of 1.", |
| method.getSimpleName(), |
| parameters.size()); |
| } |
| TypeMirror subjectType = getErasureAsMirror(encodedType, env); |
| |
| // The method should be able to accept a value of encodedType; |
| TypeMirror parameterType = getErasureAsMirror(parameters.get(0).asType(), env); |
| if (!env.getTypeUtils().isAssignable(subjectType, parameterType)) { |
| throw new SerializationProcessingException( |
| encodedType, |
| "%s is tagged @Interner, but cannot accept a value of type %s because it is not" |
| + " assignable to %s.", |
| method.getSimpleName(), |
| encodedType, |
| parameterType); |
| } |
| |
| // The method should return a value that can be assigned to encodedType. |
| TypeMirror returnType = getErasureAsMirror(method.getReturnType(), env); |
| if (!env.getTypeUtils().isAssignable(returnType, subjectType)) { |
| throw new SerializationProcessingException( |
| encodedType, |
| "%s is tagged @Interner, but its return type %s cannot be assigned to type %s.", |
| method.getSimpleName(), |
| method.getReturnType(), |
| encodedType); |
| } |
| } |
| |
| private MethodSpec buildSerializeMethodWithInstantiator( |
| TypeElement encodedType, List<? extends VariableElement> fields, AutoCodec annotation) |
| throws SerializationProcessingException { |
| MethodSpec.Builder serializeBuilder = |
| Initializers.initializeSerializeMethodBuilder(encodedType, annotation, env); |
| for (VariableElement parameter : fields) { |
| Optional<FieldValueAndClass> hasField = |
| getFieldByNameRecursive(encodedType, parameter.getSimpleName().toString()); |
| if (hasField.isPresent()) { |
| if (findRelationWithGenerics(hasField.get().value.asType(), parameter.asType()) |
| == Relation.UNRELATED_TO) { |
| throw new SerializationProcessingException( |
| parameter, |
| "%s: parameter %s's type %s is unrelated to corresponding field type %s", |
| encodedType.getQualifiedName(), |
| parameter.getSimpleName(), |
| parameter.asType(), |
| hasField.get().value.asType()); |
| } |
| TypeKind typeKind = parameter.asType().getKind(); |
| serializeBuilder.addStatement( |
| "$T unsafe_$L = ($T) $T.unsafe().get$L(obj, $L_offset)", |
| sanitizeTypeParameter(parameter.asType(), env), |
| parameter.getSimpleName(), |
| sanitizeTypeParameter(parameter.asType(), env), |
| UnsafeProvider.class, |
| typeKind.isPrimitive() ? firstLetterUpper(toLowerCase(typeKind.toString())) : "Object", |
| parameter.getSimpleName()); |
| marshallers.writeSerializationCode( |
| new SerializationCodeGenerator.Context( |
| serializeBuilder, parameter.asType(), "unsafe_" + parameter.getSimpleName())); |
| } else { |
| addSerializeParameterWithGetter(encodedType, parameter, serializeBuilder); |
| } |
| } |
| return serializeBuilder.build(); |
| } |
| |
| private String findGetterForClass(VariableElement parameter, TypeElement type) |
| throws SerializationProcessingException { |
| List<ExecutableElement> methods = |
| ElementFilter.methodsIn(env.getElementUtils().getAllMembers(type)); |
| |
| ImmutableSet.Builder<String> possibleGetterNamesBuilder = |
| ImmutableSet.<String>builder().add(parameter.getSimpleName().toString()); |
| |
| if (parameter.asType().getKind() == TypeKind.BOOLEAN) { |
| possibleGetterNamesBuilder.add( |
| addCamelCasePrefix(parameter.getSimpleName().toString(), "is")); |
| } else { |
| possibleGetterNamesBuilder.add( |
| addCamelCasePrefix(parameter.getSimpleName().toString(), "get")); |
| } |
| ImmutableSet<String> possibleGetterNames = possibleGetterNamesBuilder.build(); |
| |
| for (ExecutableElement element : methods) { |
| if (!element.getModifiers().contains(Modifier.STATIC) |
| && !element.getModifiers().contains(Modifier.PRIVATE) |
| && possibleGetterNames.contains(element.getSimpleName().toString()) |
| && findRelationWithGenerics(parameter.asType(), element.getReturnType()) |
| != Relation.UNRELATED_TO) { |
| return element.getSimpleName().toString(); |
| } |
| } |
| |
| throw new SerializationProcessingException( |
| parameter, |
| "%s: No getter found corresponding to parameter %s, %s", |
| type, |
| parameter.getSimpleName(), |
| parameter.asType()); |
| } |
| |
| private static String addCamelCasePrefix(String name, String prefix) { |
| return prefix + firstLetterUpper(name); |
| } |
| |
| private static String firstLetterUpper(String str) { |
| return Character.toUpperCase(str.charAt(0)) + (str.length() == 1 ? "" : str.substring(1)); |
| } |
| |
| private void addSerializeParameterWithGetter( |
| TypeElement encodedType, VariableElement parameter, MethodSpec.Builder serializeBuilder) |
| throws SerializationProcessingException { |
| String getter = turnGetterIntoExpression(findGetterForClass(parameter, encodedType)); |
| marshallers.writeSerializationCode( |
| new Marshaller.Context(serializeBuilder, parameter.asType(), getter)); |
| } |
| |
| private static String turnGetterIntoExpression(String getterName) { |
| return "obj." + getterName + "()"; |
| } |
| |
| private MethodSpec buildSerializeMethodWithInstantiatorForAutoValue( |
| TypeElement encodedType, List<? extends VariableElement> fields, AutoCodec annotation) |
| throws SerializationProcessingException { |
| MethodSpec.Builder serializeBuilder = |
| Initializers.initializeSerializeMethodBuilder(encodedType, annotation, env); |
| for (VariableElement parameter : fields) { |
| addSerializeParameterWithGetter(encodedType, parameter, serializeBuilder); |
| } |
| return serializeBuilder.build(); |
| } |
| |
| /** |
| * Adds a body to the deserialize method that extracts serialized parameters. |
| * |
| * <p>Parameter values are extracted into local variables with the same name as the parameter |
| * suffixed with a trailing underscore. For example, {@code target} becomes {@code target_}. This |
| * is to avoid name collisions with variables used internally by AutoCodec. |
| */ |
| private void buildDeserializeBody( |
| MethodSpec.Builder builder, List<? extends VariableElement> fields) |
| throws SerializationProcessingException { |
| for (VariableElement parameter : fields) { |
| String paramName = parameter.getSimpleName() + "_"; |
| marshallers.writeDeserializationCode( |
| new Marshaller.Context(builder, parameter.asType(), paramName)); |
| } |
| } |
| |
| /** Invokes the instantiator and returns the value. */ |
| private static void addReturnNew( |
| MethodSpec.Builder builder, |
| TypeElement type, |
| ExecutableElement instantiator, |
| ProcessingEnvironment env) { |
| List<? extends TypeMirror> allThrown = instantiator.getThrownTypes(); |
| if (!allThrown.isEmpty()) { |
| builder.beginControlFlow("try"); |
| } |
| TypeName typeName = getErasure(type, env); |
| String parameters = |
| instantiator.getParameters().stream() |
| .map(AutoCodecProcessor::handleFromParameter) |
| .collect(Collectors.joining(", ")); |
| if (instantiator.getKind().equals(ElementKind.CONSTRUCTOR)) { |
| builder.addStatement("return new $T($L)", typeName, parameters); |
| } else { // Otherwise, it's a factory method. |
| builder.addStatement("return $T.$L($L)", typeName, instantiator.getSimpleName(), parameters); |
| } |
| if (!allThrown.isEmpty()) { |
| for (TypeMirror thrown : allThrown) { |
| builder.nextControlFlow("catch ($T e)", TypeName.get(thrown)); |
| builder.addStatement( |
| "throw new $T(\"$L instantiator threw an exception\", e)", |
| SerializationException.class, |
| type.getQualifiedName()); |
| } |
| builder.endControlFlow(); |
| } |
| } |
| |
| /** Converts a constructor parameter to a String representing its handle within deserialize. */ |
| private static String handleFromParameter(VariableElement parameter) { |
| return parameter.getSimpleName() + "_"; |
| } |
| |
| /** |
| * Adds fields to the codec class to hold offsets and adds a constructor to initialize them. |
| * |
| * <p>For a parameter with name {@code target}, the field will have name {@code target_offset}. |
| * |
| * @param parameters constructor parameters |
| */ |
| private void initializeUnsafeOffsets( |
| TypeSpec.Builder builder, |
| TypeElement encodedType, |
| List<? extends VariableElement> parameters) { |
| MethodSpec.Builder constructor = MethodSpec.constructorBuilder(); |
| for (VariableElement param : parameters) { |
| Optional<FieldValueAndClass> field = |
| getFieldByNameRecursive(encodedType, param.getSimpleName().toString()); |
| if (!field.isPresent()) { |
| // Will attempt to use a getter for this field instead. |
| continue; |
| } |
| builder.addField( |
| TypeName.LONG, param.getSimpleName() + "_offset", Modifier.PRIVATE, Modifier.FINAL); |
| constructor.beginControlFlow("try"); |
| constructor.addStatement( |
| "this.$L_offset = $T.unsafe().objectFieldOffset($T.class.getDeclaredField(\"$L\"))", |
| param.getSimpleName(), |
| UnsafeProvider.class, |
| ClassName.get(field.get().declaringClassType), |
| param.getSimpleName()); |
| constructor.nextControlFlow("catch ($T e)", NoSuchFieldException.class); |
| constructor.addStatement("throw new $T(e)", IllegalStateException.class); |
| constructor.endControlFlow(); |
| } |
| builder.addMethod(constructor.build()); |
| } |
| |
| /** The value of a field, as well as the class that directly declares it. */ |
| private static class FieldValueAndClass { |
| final VariableElement value; |
| final TypeElement declaringClassType; |
| |
| FieldValueAndClass(VariableElement value, TypeElement declaringClassType) { |
| this.value = value; |
| this.declaringClassType = declaringClassType; |
| } |
| } |
| |
| private Optional<FieldValueAndClass> getFieldByNameRecursive(TypeElement type, String name) { |
| Optional<VariableElement> field = |
| ElementFilter.fieldsIn(type.getEnclosedElements()).stream() |
| .filter(f -> f.getSimpleName().contentEquals(name)) |
| .findAny(); |
| |
| if (field.isPresent()) { |
| return Optional.of(new FieldValueAndClass(field.get(), type)); |
| } |
| if (type.getSuperclass().getKind() != TypeKind.NONE) { |
| // Applies the erased superclass type so that it can be used in `T.class`. |
| return getFieldByNameRecursive( |
| (TypeElement) |
| env.getTypeUtils().asElement(env.getTypeUtils().erasure(type.getSuperclass())), |
| name); |
| } |
| return Optional.empty(); |
| } |
| |
| /** Emits a note to BUILD log during annotation processing for debugging. */ |
| private void note(String note) { |
| env.getMessager().printMessage(Diagnostic.Kind.NOTE, note); |
| } |
| } |