blob: a7693f4e4b7f0b658bca83c58e9d215a9cb16c58 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.ignite.internal.network.processor.messages;
import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.MethodSpec.Builder;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import org.apache.ignite.internal.network.processor.MessageClass;
import org.apache.ignite.internal.network.processor.MessageGroupWrapper;
import org.apache.ignite.internal.network.processor.ProcessingException;
import org.apache.ignite.internal.network.processor.TypeUtils;
import org.apache.ignite.internal.tostring.IgniteToStringInclude;
import org.apache.ignite.internal.tostring.S;
import org.apache.ignite.network.NetworkMessage;
import org.apache.ignite.network.annotations.Marshallable;
import org.apache.ignite.network.annotations.WithSetter;
import org.jetbrains.annotations.Nullable;
/**
* Class for generating implementations of the {@link NetworkMessage} interfaces and their builders, generated by a {@link
* MessageBuilderGenerator}.
*/
public class MessageImplGenerator {
/** Type name of the {@code byte[]}. */
static final ArrayTypeName BYTE_ARRAY_TYPE = ArrayTypeName.of(TypeName.BYTE);
/** Processing environment. */
private final ProcessingEnvironment processingEnv;
/** Message group. */
private final MessageGroupWrapper messageGroup;
private final TypeUtils typeUtils;
private final MarshallableTypesBlackList marshallableTypesBlackList;
/**
* Constructor.
*
* @param processingEnv Processing environment.
* @param messageGroup Message group.
*/
public MessageImplGenerator(ProcessingEnvironment processingEnv, MessageGroupWrapper messageGroup) {
this.processingEnv = processingEnv;
this.messageGroup = messageGroup;
this.typeUtils = new TypeUtils(processingEnv);
this.marshallableTypesBlackList = new MarshallableTypesBlackList(typeUtils);
}
/**
* Generates the implementation of a given Network Message interface and its Builder (as a nested class).
*
* @param message network message
* @param builderInterface generated builder interface
* @return {@code TypeSpec} of the generated message implementation
*/
public TypeSpec generateMessageImpl(MessageClass message, TypeSpec builderInterface) {
ClassName messageImplClassName = message.implClassName();
processingEnv.getMessager()
.printMessage(Diagnostic.Kind.NOTE, "Generating " + messageImplClassName);
List<ExecutableElement> getters = message.getters();
var fields = new ArrayList<FieldSpec>(getters.size());
var methodImpls = new ArrayList<MethodSpec>(getters.size());
var notNullFieldNames = new HashSet<String>();
var marshallableFieldNames = new HashSet<String>();
// create a field and a getter implementation for every getter in the message interface
for (ExecutableElement getter : getters) {
TypeMirror getterType = getter.getReturnType();
TypeName getterReturnType = TypeName.get(getterType);
String getterName = getter.getSimpleName().toString();
boolean isMarshallable = getter.getAnnotation(Marshallable.class) != null;
if (isMarshallable && !marshallableTypesBlackList.canBeMarshallable(getterType)) {
String error = String.format(
"\"%s\" field is marked as @Marshallable but this type is supported by native serialization, "
+ "remove this annotation from the field",
getterName
);
throw new ProcessingException(error);
}
FieldSpec.Builder fieldBuilder = FieldSpec.builder(getterReturnType, getterName)
.addAnnotation(IgniteToStringInclude.class)
.addModifiers(Modifier.PRIVATE);
boolean generateSetter = getter.getAnnotation(WithSetter.class) != null;
if (!isMarshallable && !generateSetter) {
fieldBuilder.addModifiers(Modifier.FINAL);
}
if (requiresNotNullCheck(getter)) {
notNullFieldNames.add(getterName);
}
FieldSpec field = fieldBuilder.build();
fields.add(field);
if (isMarshallable) {
marshallableFieldNames.add(getterName);
String name = getByteArrayFieldName(getterName);
FieldSpec marshallableFieldArray = FieldSpec.builder(BYTE_ARRAY_TYPE, name)
.addModifiers(Modifier.PRIVATE)
.build();
fields.add(marshallableFieldArray);
MethodSpec baGetterImpl = MethodSpec.methodBuilder(name)
.returns(BYTE_ARRAY_TYPE)
.addStatement("return $N", marshallableFieldArray)
.build();
methodImpls.add(baGetterImpl);
}
if (generateSetter) {
MethodSpec setterImpl = MethodSpec.methodBuilder(getterName)
.returns(TypeName.VOID)
.addModifiers(Modifier.PUBLIC)
.addParameter(getterReturnType, getterName)
.addAnnotation(Override.class)
.addStatement("this.$L = $L", getterName, getterName)
.build();
methodImpls.add(setterImpl);
}
MethodSpec getterImpl = MethodSpec.overriding(getter)
.addStatement("return $N", field)
.build();
methodImpls.add(getterImpl);
}
TypeSpec.Builder messageImpl = TypeSpec.classBuilder(messageImplClassName)
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(message.className())
.addFields(fields)
.addMethods(methodImpls)
.addMethod(constructor(fields, notNullFieldNames, marshallableFieldNames));
// group type constant and getter
FieldSpec groupTypeField = FieldSpec.builder(short.class, "GROUP_TYPE")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$L", messageGroup.groupType())
.build();
messageImpl.addField(groupTypeField);
MethodSpec groupTypeMethod = MethodSpec.methodBuilder("groupType")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(short.class)
.addStatement("return $N", groupTypeField)
.build();
messageImpl.addMethod(groupTypeMethod);
// TODO: https://issues.apache.org/jira/browse/IGNITE-17591
MethodSpec toStringMethod = MethodSpec.methodBuilder("toString")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(String.class)
.addStatement("return $T.toString($T.class, this)", S.class, messageImplClassName)
.build();
messageImpl.addMethod(toStringMethod);
// message type constant and getter
FieldSpec messageTypeField = FieldSpec.builder(short.class, "TYPE")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$L", message.messageType())
.build();
messageImpl.addField(messageTypeField);
MethodSpec messageTypeMethod = MethodSpec.methodBuilder("messageType")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(short.class)
.addStatement("return $N", messageTypeField)
.build();
messageImpl.addMethod(messageTypeMethod);
// equals and hashCode
generateEqualsAndHashCode(messageImpl, message);
var builderName = ClassName.get(message.packageName(), builderInterface.name);
// nested builder interface and static factory method
TypeSpec builder = generateBuilderImpl(message, messageImplClassName, builderName, notNullFieldNames, marshallableFieldNames);
messageImpl.addType(builder);
MethodSpec builderMethod = MethodSpec.methodBuilder("builder")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(builderName)
.addStatement("return new $N()", builder)
.build();
messageImpl.addMethod(builderMethod);
generatePrepareMarshal(messageImpl, message);
generateUnmarshalMethod(messageImpl, message);
messageImpl
.addOriginatingElement(message.element())
.addOriginatingElement(messageGroup.element());
return messageImpl.build();
}
/**
* Resolves type of an object to a type that may hold a message. Returns {@code null} if the type
* can't hold a message.
*
* @param parameterType Type.
* @return {@link MaybeMessageType} or {@code null} if the type can't hold a message.
*/
private Optional<MaybeMessageType> resolveType(TypeMirror parameterType) {
if (parameterType.getKind() == TypeKind.ARRAY) {
if (!((ArrayType) parameterType).getComponentType().getKind().isPrimitive()) {
return Optional.of(MaybeMessageType.OBJECT_ARRAY);
}
} else if (parameterType.getKind() == TypeKind.DECLARED) {
if (typeUtils.isSubType(parameterType, Collection.class)) {
return Optional.of(MaybeMessageType.COLLECTION);
} else if (typeUtils.isSameType(parameterType, Map.class)) {
return Optional.of(MaybeMessageType.MAP);
} else if (typeUtils.isSubType(parameterType, NetworkMessage.class)) {
return Optional.of(MaybeMessageType.MESSAGE);
}
}
return Optional.empty();
}
private void generatePrepareMarshal(TypeSpec.Builder messageImplBuild, MessageClass message) {
boolean isNeeded = false;
ClassName setType = ClassName.get(IntSet.class);
Builder prepareMarshal = MethodSpec.methodBuilder("prepareMarshal")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addException(Exception.class)
.addParameter(setType, "usedDescriptors")
.addParameter(Object.class, "marshallerObj");
String marshallerPackage = "org.apache.ignite.internal.network.serialization.marshal";
ClassName marshallerClass = ClassName.get(marshallerPackage, "UserObjectMarshaller");
ClassName marshalledObjectClass = ClassName.get(marshallerPackage, "MarshalledObject");
prepareMarshal.addStatement("$T marshaller = ($T) marshallerObj", marshallerClass, marshallerClass);
for (ExecutableElement executableElement : message.getters()) {
TypeMirror type = executableElement.getReturnType();
String objectName = executableElement.getSimpleName().toString();
if (executableElement.getAnnotation(Marshallable.class) != null) {
isNeeded = true;
String baName = getByteArrayFieldName(objectName);
String moName = baName + "mo";
prepareMarshal.addStatement("$T $N = marshaller.marshal($N)", marshalledObjectClass, moName, objectName);
prepareMarshal.addStatement("usedDescriptors.addAll($N.usedDescriptorIds())", moName);
prepareMarshal.addStatement("$N = $N.bytes()", baName, moName).addCode("\n");
} else {
Optional<MaybeMessageType> objectType = resolveType(type);
if (objectType.isEmpty()) {
continue;
}
switch (objectType.get()) {
case OBJECT_ARRAY:
isNeeded = generateObjectArrayHandler((ArrayType) type, prepareMarshal, objectName,
"prepareMarshal(usedDescriptors, marshaller)") || isNeeded;
break;
case COLLECTION:
isNeeded = generateCollectionHandler((DeclaredType) type, prepareMarshal, objectName,
"prepareMarshal(usedDescriptors, marshaller)") || isNeeded;
break;
case MESSAGE:
isNeeded = generateMessageHandler(prepareMarshal, objectName,
"prepareMarshal(usedDescriptors, marshaller)") || isNeeded;
break;
case MAP:
isNeeded = generateMapHandler(prepareMarshal, (DeclaredType) type, objectName,
"prepareMarshal(usedDescriptors, marshaller)") || isNeeded;
break;
default:
break;
}
}
}
if (isNeeded) {
messageImplBuild.addMethod(prepareMarshal.build());
}
}
private void generateUnmarshalMethod(TypeSpec.Builder messageImplBuild, MessageClass message) {
boolean isNeeded = false;
Builder unmarshal = MethodSpec.methodBuilder("unmarshal")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addException(Exception.class)
.addParameter(Object.class, "marshallerObj")
.addParameter(Object.class, "descriptorsObj");
String uosPackage = "org.apache.ignite.internal.network.serialization.marshal";
ClassName marshallerClass = ClassName.get(uosPackage, "UserObjectMarshaller");
unmarshal.addStatement("$T marshaller = ($T) marshallerObj", marshallerClass, marshallerClass);
for (ExecutableElement executableElement : message.getters()) {
TypeMirror type = executableElement.getReturnType();
String objectName = executableElement.getSimpleName().toString();
if (executableElement.getAnnotation(Marshallable.class) != null) {
isNeeded = true;
String baName = getByteArrayFieldName(objectName);
unmarshal.addStatement("$N = marshaller.unmarshal($N, descriptorsObj)", objectName, baName);
unmarshal.addStatement("$N = null", baName);
} else {
Optional<MaybeMessageType> objectType = resolveType(type);
if (objectType.isEmpty()) {
continue;
}
switch (objectType.get()) {
case OBJECT_ARRAY:
isNeeded = generateObjectArrayHandler((ArrayType) type, unmarshal, objectName,
"unmarshal(marshaller, descriptorsObj)") || isNeeded;
break;
case COLLECTION:
isNeeded = generateCollectionHandler((DeclaredType) type, unmarshal, objectName,
"unmarshal(marshaller, descriptorsObj)") || isNeeded;
break;
case MESSAGE:
isNeeded = generateMessageHandler(unmarshal, objectName,
"unmarshal(marshaller, descriptorsObj)") || isNeeded;
break;
case MAP:
isNeeded = generateMapHandler(unmarshal, (DeclaredType) type, objectName,
"unmarshal(marshaller, descriptorsObj)") || isNeeded;
break;
default:
break;
}
}
}
if (isNeeded) {
messageImplBuild.addMethod(unmarshal.build());
}
}
private boolean generateObjectArrayHandler(ArrayType type, Builder methodBuilder, String objectName, String code) {
TypeMirror componentType = type.getComponentType();
if (typeUtils.isSubType(componentType, NetworkMessage.class)) {
methodBuilder.beginControlFlow("if ($N != null)", objectName);
methodBuilder.beginControlFlow("for ($T obj : $N)", componentType, objectName);
methodBuilder.addStatement("if (obj != null) obj." + code);
methodBuilder.endControlFlow();
methodBuilder.endControlFlow().addCode("\n");
return true;
}
return false;
}
private boolean generateCollectionHandler(DeclaredType type, Builder methodBuilder, String objectName, String code) {
TypeMirror elementType = type.getTypeArguments().get(0);
if (typeUtils.isSubType(elementType, NetworkMessage.class)) {
methodBuilder.beginControlFlow("if ($N != null)", objectName);
methodBuilder.beginControlFlow("for ($T obj : $N)", elementType, objectName);
methodBuilder.addStatement("if (obj != null) obj." + code, objectName);
methodBuilder.endControlFlow();
methodBuilder.endControlFlow().addCode("\n");
return true;
}
return false;
}
private boolean generateMessageHandler(Builder methodBuilder, String objectName, String code) {
methodBuilder.addStatement("if ($N != null) $N." + code, objectName, objectName);
return true;
}
private boolean generateMapHandler(Builder methodBuilder, DeclaredType type, String objectName, String code) {
TypeMirror keyType = type.getTypeArguments().get(0);
boolean keyIsMessage = typeUtils.isSubType(keyType, NetworkMessage.class);
TypeMirror valueType = type.getTypeArguments().get(1);
boolean valueIsMessage = typeUtils.isSubType(valueType, NetworkMessage.class);
if (keyIsMessage || valueIsMessage) {
ParameterizedTypeName entryType = ParameterizedTypeName.get(
ClassName.get(Entry.class),
TypeName.get(keyType),
TypeName.get(valueType)
);
ParameterizedTypeName entrySetType = ParameterizedTypeName.get(ClassName.get(Set.class), entryType);
String entrySetName = objectName + "EntrySet";
methodBuilder.beginControlFlow("if ($N != null)", objectName);
methodBuilder.addStatement("$T $N = $N.entrySet()", entrySetType, entrySetName, objectName);
methodBuilder.beginControlFlow("for ($T entry : $N)", entryType, entrySetName);
methodBuilder.addStatement("$T key = entry.getKey()", keyType);
methodBuilder.addStatement("$T value = entry.getValue()", valueType);
if (keyIsMessage) {
methodBuilder.addStatement("if (key != null) key." + code);
}
if (valueIsMessage) {
methodBuilder.addStatement("if (value != null) value." + code);
}
methodBuilder.endControlFlow();
methodBuilder.endControlFlow().addCode("\n");
return true;
}
return false;
}
/**
* Generates implementations of {@link #hashCode} and {@link #equals} for the provided {@code message} and adds them to the provided
* builder.
*/
private static void generateEqualsAndHashCode(TypeSpec.Builder messageImplBuilder, MessageClass message) {
MethodSpec.Builder equals = MethodSpec.methodBuilder("equals")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(boolean.class)
.addParameter(Object.class, "other")
.addCode(CodeBlock.builder()
.beginControlFlow("if (this == other)")
.addStatement("return true")
.endControlFlow()
.build())
.addCode(CodeBlock.builder()
.beginControlFlow("if (other == null || getClass() != other.getClass())")
.addStatement("return false")
.endControlFlow()
.build());
MethodSpec.Builder hashCode = MethodSpec.methodBuilder("hashCode")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(int.class);
if (message.getters().isEmpty()) {
equals.addStatement("return true");
hashCode.addStatement("return $T.class.hashCode()", message.implClassName());
messageImplBuilder
.addMethod(equals.build())
.addMethod(hashCode.build());
return;
}
var arrays = new ArrayList<ExecutableElement>();
var primitives = new ArrayList<ExecutableElement>();
var others = new ArrayList<ExecutableElement>();
for (ExecutableElement element : message.getters()) {
TypeKind typeKind = element.getReturnType().getKind();
if (typeKind.isPrimitive()) {
primitives.add(element);
} else if (typeKind == TypeKind.ARRAY) {
arrays.add(element);
} else {
others.add(element);
}
}
CodeBlock.Builder comparisonStatement = CodeBlock.builder().add("return ");
boolean first = true;
// objects are compared using "Objects.equals"
for (ExecutableElement other : others) {
if (first) {
first = false;
} else {
comparisonStatement.add(" && ");
}
String fieldName = other.getSimpleName().toString();
comparisonStatement.add("$T.equals(this.$L, otherMessage.$L)", Objects.class, fieldName, fieldName);
}
// arrays are compared using "Arrays.equals"
for (ExecutableElement array : arrays) {
if (first) {
first = false;
} else {
comparisonStatement.add(" && ");
}
String fieldName = array.getSimpleName().toString();
comparisonStatement.add("$T.equals(this.$L, otherMessage.$L)", Arrays.class, fieldName, fieldName);
}
// primitives are compared using "==", except for floating point values (because of NaNs and stuff)
for (ExecutableElement primitive : primitives) {
if (first) {
first = false;
} else {
comparisonStatement.add(" && ");
}
String fieldName = primitive.getSimpleName().toString();
switch (primitive.getReturnType().getKind()) {
case FLOAT:
comparisonStatement.add("$T.compare(this.$L, otherMessage.$L) == 0", Float.class, fieldName, fieldName);
break;
case DOUBLE:
comparisonStatement.add("$T.compare(this.$L, otherMessage.$L) == 0", Double.class, fieldName, fieldName);
break;
default:
comparisonStatement.add("this.$L == otherMessage.$L", fieldName, fieldName);
break;
}
}
equals
.addStatement("var otherMessage = ($T)other", message.implClassName())
.addStatement(comparisonStatement.build());
hashCode
.addStatement("int result = 0");
// primitives can be boxed and used in "Objects.hash"
String objectHashCode = Stream.concat(primitives.stream(), others.stream())
.map(element -> "this." + element.getSimpleName())
.collect(Collectors.joining(", ", "result = $T.hash(", ")"));
if (!objectHashCode.isEmpty()) {
hashCode.addStatement(objectHashCode, Objects.class);
}
for (ExecutableElement array : arrays) {
hashCode.addStatement("result = 31 * result + $T.hashCode(this.$L)", Arrays.class, array.getSimpleName());
}
hashCode.addStatement("return result");
messageImplBuilder
.addMethod(equals.build())
.addMethod(hashCode.build());
}
/**
* Creates a constructor for the current Network Message implementation.
*/
private static MethodSpec constructor(List<FieldSpec> fields, Set<String> notNullFieldNames, Set<String> marshallableFieldNames) {
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PRIVATE);
fields.forEach(field -> {
String fieldName = field.name;
if (notNullFieldNames.contains(fieldName) && marshallableFieldNames.contains(fieldName)) {
CodeBlock nullCheck = CodeBlock.builder()
.beginControlFlow("if ($L == null && $L == null)", fieldName, getByteArrayFieldName(fieldName))
.addStatement("throw new $T($S)", NullPointerException.class, fieldName + " is not marked @Nullable")
.endControlFlow()
.build();
constructor.addCode(nullCheck);
}
constructor
.addParameter(field.type, fieldName)
.addStatement("this.$N = $N", field, field);
});
return constructor.build();
}
/**
* Generates a nested static class that implements the Builder interface, generated during previous steps.
*/
private static TypeSpec generateBuilderImpl(
MessageClass message, ClassName messageImplClass, ClassName builderName, Set<String> notNullFieldNames,
Set<String> marshallableFieldNames) {
List<ExecutableElement> messageGetters = message.getters();
var fields = new ArrayList<FieldSpec>(messageGetters.size());
var setters = new ArrayList<MethodSpec>(messageGetters.size());
var getters = new ArrayList<MethodSpec>(messageGetters.size());
for (ExecutableElement messageGetter : messageGetters) {
boolean isMarshallable = messageGetter.getAnnotation(Marshallable.class) != null;
var getterReturnType = TypeName.get(messageGetter.getReturnType());
String getterName = messageGetter.getSimpleName().toString();
FieldSpec field = FieldSpec.builder(getterReturnType, getterName)
.addModifiers(Modifier.PRIVATE).build();
fields.add(field);
Builder setterBuilder = MethodSpec.methodBuilder(getterName)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(builderName)
.addParameter(getterReturnType, getterName);
if (notNullFieldNames.contains(getterName)) {
setterBuilder.addStatement("$T.requireNonNull($L, $S)", Objects.class, getterName, getterName + " is not marked @Nullable");
}
setterBuilder.addStatement("this.$N = $L", field, getterName)
.addStatement("return this");
MethodSpec setter = setterBuilder.build();
setters.add(setter);
MethodSpec getter = MethodSpec.methodBuilder(getterName)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(getterReturnType)
.addStatement("return $N", field)
.build();
getters.add(getter);
if (isMarshallable) {
String name = getByteArrayFieldName(getterName);
FieldSpec baField = FieldSpec.builder(BYTE_ARRAY_TYPE, name)
.addModifiers(Modifier.PRIVATE)
.build();
fields.add(baField);
MethodSpec baSetter = MethodSpec.methodBuilder(name)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(builderName)
.addParameter(BYTE_ARRAY_TYPE, name)
.addStatement("this.$N = $L", baField, name)
.addStatement("return this")
.build();
setters.add(baSetter);
MethodSpec baGetter = MethodSpec.methodBuilder(name)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(BYTE_ARRAY_TYPE)
.addStatement("return $N", baField)
.build();
getters.add(baGetter);
}
}
return TypeSpec.classBuilder("Builder")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
.addSuperinterface(builderName)
.addFields(fields)
.addMethods(setters)
.addMethods(getters)
.addMethod(buildMethod(message, messageImplClass, fields, notNullFieldNames, marshallableFieldNames))
.build();
}
/**
* Generates the {@code build()} method for the Builder interface implementation.
*/
private static MethodSpec buildMethod(MessageClass message, ClassName messageImplClass, List<FieldSpec> fields,
Set<String> notNullFieldNames, Set<String> marshallableFieldNames) {
CodeBlock.Builder methodBodyBuilder = CodeBlock.builder();
methodBodyBuilder.add("return new $T", messageImplClass);
CodeBlock constructorArgs = fields.stream()
.map(field -> {
String fieldName = field.name;
if (notNullFieldNames.contains(fieldName) && !marshallableFieldNames.contains(fieldName)) {
return CodeBlock.of("$T.requireNonNull($L, $S)", Objects.class, fieldName, fieldName + " is not marked @Nullable");
}
return CodeBlock.of("$L", fieldName);
})
.collect(CodeBlock.joining(", ", "(", ")"));
methodBodyBuilder.add(constructorArgs);
return MethodSpec.methodBuilder("build")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(message.className())
.addStatement(methodBodyBuilder.build())
.build();
}
private static boolean requiresNotNullCheck(ExecutableElement el) {
TypeMirror returnType = el.getReturnType();
TypeKind kind = returnType.getKind();
if (kind == TypeKind.ARRAY) {
List<? extends AnnotationMirror> annotations = returnType.getAnnotationMirrors();
for (AnnotationMirror annotation : annotations) {
DeclaredType annotationType = annotation.getAnnotationType();
if (Nullable.class.getName().equals(annotationType.toString())) {
return false;
}
}
return true;
}
return !kind.isPrimitive() && el.getAnnotation(Nullable.class) == null;
}
public static String getByteArrayFieldName(String objectName) {
return objectName + "ByteArray";
}
/** Types that may hold network message. */
private enum MaybeMessageType {
OBJECT_ARRAY,
COLLECTION,
MESSAGE,
MAP;
}
}