Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix naming conversion and add null get #6

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import com.salesforce.jprotoc.ProtocPlugin;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
Expand All @@ -43,32 +42,45 @@ public class OptionalGenerator extends Generator {
private static final String CLASS_SCOPE = "class_scope:";
private static final String DEFAULT_OPTIONAL_CLASS = Optional.class.getName();
private static final String DEFAULT_OPTIONAL_GETTER_METHOD = "get";
private static final Map<JavaType, String> PRIMITIVE_CLASSES = ImmutableMap.<JavaType, String>builder()
.put(JavaType.INT, Integer.class.getSimpleName())
.put(JavaType.LONG, Long.class.getSimpleName())
.put(JavaType.FLOAT, Float.class.getSimpleName())
.put(JavaType.DOUBLE, Double.class.getSimpleName())
.put(JavaType.BOOLEAN, Boolean.class.getSimpleName())
.put(JavaType.STRING, String.class.getSimpleName())
.put(JavaType.BYTE_STRING, ByteString.class.getName())
.build();
private static final Map<String, String> PRIMITIVE_OPTIONALS = ImmutableMap.<String, String>builder()
.put(Integer.class.getSimpleName(), OptionalInt.class.getName())
.put(Long.class.getSimpleName(), OptionalLong.class.getName())
.put(Double.class.getSimpleName(), OptionalDouble.class.getName())
.build();
private static final Map<String, String> PRIMITIVE_OPTIONAL_GETTER_METHODS = ImmutableMap.<String, String>builder()
.put(Integer.class.getSimpleName(), "getAsInt")
.put(Long.class.getSimpleName(), "getAsLong")
.put(Double.class.getSimpleName(), "getAsDouble")
.build();
private static final Map<JavaType, String> PRIMITIVE_CLASSES =
ImmutableMap.<JavaType, String>builder()
.put(JavaType.INT, Integer.class.getSimpleName())
.put(JavaType.LONG, Long.class.getSimpleName())
.put(JavaType.FLOAT, Float.class.getSimpleName())
.put(JavaType.DOUBLE, Double.class.getSimpleName())
.put(JavaType.BOOLEAN, Boolean.class.getSimpleName())
.put(JavaType.STRING, String.class.getSimpleName())
.put(JavaType.BYTE_STRING, ByteString.class.getName())
.build();
private static final Map<String, String> PRIMITIVE_OPTIONALS =
ImmutableMap.<String, String>builder()
.put(Integer.class.getSimpleName(), OptionalInt.class.getName())
.put(Long.class.getSimpleName(), OptionalLong.class.getName())
.put(Double.class.getSimpleName(), OptionalDouble.class.getName())
.build();
private static final Map<String, String> PRIMITIVE_OPTIONAL_GETTER_METHODS =
ImmutableMap.<String, String>builder()
.put(Integer.class.getSimpleName(), "getAsInt")
.put(Long.class.getSimpleName(), "getAsLong")
.put(Double.class.getSimpleName(), "getAsDouble")
.build();
private Parameters parameters;
private ProtoTypeMap protoTypeMap;

public static void main(String[] args) {
ProtocPlugin.generate(new OptionalGenerator());
}

private Parameters parameters;
private ProtoTypeMap protoTypeMap;
private static boolean hasFieldPresence(FieldDescriptorProto fieldDescriptor) {
return fieldDescriptor.getLabel() != Label.LABEL_REPEATED
&& (fieldDescriptor.getProto3Optional()
|| fieldDescriptor.getType() == Type.TYPE_MESSAGE
|| fieldDescriptor.hasOneofIndex());
}

private static String templatePath(String path) {
return TEMPLATES_DIRECTORY + path;
}

@Override
public List<File> generateFiles(CodeGeneratorRequest request) throws GeneratorException {
Expand All @@ -89,21 +101,25 @@ protected List<Feature> supportedFeatures() {

private Stream<File> handleProtoFile(FileDescriptorProto fileDescriptor) {
String protoPackage = fileDescriptor.getPackage();
String javaPackage = fileDescriptor.getOptions().hasJavaPackage()
? fileDescriptor.getOptions().getJavaPackage()
: protoPackage;
String javaPackage =
fileDescriptor.getOptions().hasJavaPackage()
? fileDescriptor.getOptions().getJavaPackage()
: protoPackage;

return fileDescriptor.getMessageTypeList().stream()
.flatMap(descriptor -> handleMessage(descriptor, getFileName(fileDescriptor, descriptor), protoPackage, javaPackage));
.flatMap(
descriptor ->
handleMessage(
descriptor,
getFileName(fileDescriptor, descriptor),
protoPackage,
javaPackage));
}

private Stream<File> handleMessage(
DescriptorProto messageDescriptor,
String fileName,
String protoPackage,
String javaPackage
) {
String javaPackagePath = javaPackage.isEmpty() ? "" : javaPackage.replace(".", DIR_SEPARATOR) + DIR_SEPARATOR;
DescriptorProto messageDescriptor, String fileName, String protoPackage, String javaPackage) {
String javaPackagePath =
javaPackage.isEmpty() ? "" : javaPackage.replace(".", DIR_SEPARATOR) + DIR_SEPARATOR;
String protoPackagePath = protoPackage.isEmpty() ? "" : protoPackage + ".";
String filePath = javaPackagePath + fileName + JAVA_EXTENSION;
String fullMethodName = protoPackagePath + messageDescriptor.getName();
Expand All @@ -112,13 +128,22 @@ private Stream<File> handleMessage(
handleSingleMessage(messageDescriptor, filePath, fullMethodName),
messageDescriptor.getNestedTypeList().stream()
.filter(nestedDescriptor -> !nestedDescriptor.getOptions().getMapEntry())
.flatMap(nestedDescriptor -> handleMessage(nestedDescriptor, fileName, fullMethodName, javaPackage)));
.flatMap(
nestedDescriptor ->
handleMessage(nestedDescriptor, fileName, fullMethodName, javaPackage)));
}

private Stream<File> handleSingleMessage(DescriptorProto messageDescriptor, String filePath, String fullMethodName) {
private Stream<File> handleSingleMessage(
DescriptorProto messageDescriptor, String filePath, String fullMethodName) {
return Stream.of(
createFile(messageDescriptor, filePath, fullMethodName, BUILDER_SCOPE, this::createBuilderMethods),
createFile(messageDescriptor, filePath, fullMethodName, CLASS_SCOPE, this::createClassMethods))
createFile(
messageDescriptor,
filePath,
fullMethodName,
BUILDER_SCOPE,
this::createBuilderMethods),
createFile(
messageDescriptor, filePath, fullMethodName, CLASS_SCOPE, this::createClassMethods))
.filter(Optional::isPresent)
.map(Optional::get);
}
Expand All @@ -128,24 +153,29 @@ private Optional<File> createFile(
String fileName,
String fullMethodName,
String scopeType,
Function<FieldDescriptorProto, Optional<String>> createMethods
) {
Function<FieldDescriptorProto, Optional<String>> createMethods) {
return messageDescriptor.getFieldList().stream()
.map(createMethods)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.collectingAndThen(Collectors.joining(DELIMITER), Optional::of))
.filter(value -> !value.isEmpty())
.map(methodsContent -> File.newBuilder()
.setName(fileName)
.setContent(methodsContent + DELIMITER)
.setInsertionPoint(scopeType + fullMethodName)
.build());
.map(
methodsContent ->
File.newBuilder()
.setName(fileName)
.setContent(methodsContent + DELIMITER)
.setInsertionPoint(scopeType + fullMethodName)
.build());
}

private Optional<String> createBuilderMethods(FieldDescriptorProto fieldDescriptor) {
if (hasFieldPresence(fieldDescriptor)) {
return Stream.of(setOrClearMethod(fieldDescriptor), optionalSetOrClearMethod(fieldDescriptor), optionalGet(fieldDescriptor))
return Stream.of(
setOrClearMethod(fieldDescriptor),
optionalSetOrClearMethod(fieldDescriptor),
optionalGet(fieldDescriptor),
optionalGetNullable(fieldDescriptor))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.collectingAndThen(Collectors.joining(DELIMITER), Optional::of));
Expand All @@ -155,7 +185,10 @@ private Optional<String> createBuilderMethods(FieldDescriptorProto fieldDescript

private Optional<String> createClassMethods(FieldDescriptorProto fieldDescriptor) {
if (hasFieldPresence(fieldDescriptor)) {
return optionalGet(fieldDescriptor);
return Stream.of(optionalGet(fieldDescriptor), optionalGetNullable(fieldDescriptor))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.collectingAndThen(Collectors.joining(DELIMITER), Optional::of));
}
return Optional.empty();
}
Expand All @@ -164,10 +197,11 @@ private Optional<String> setOrClearMethod(FieldDescriptorProto fieldDescriptor)
if (!parameters.isSetterObject()) {
return Optional.empty();
}
Map<?, ?> context = ImmutableMap.builder()
.put(METHOD_NAME, getJavaMethodName(fieldDescriptor))
.put(FIELD_TYPE, getJavaTypeName(fieldDescriptor))
.build();
Map<?, ?> context =
ImmutableMap.builder()
.put(METHOD_NAME, getJavaMethodName(fieldDescriptor))
.put(FIELD_TYPE, getJavaTypeName(fieldDescriptor))
.build();
return Optional.of(applyTemplate(templatePath("setOrClear.mustache"), context));
}

Expand All @@ -177,13 +211,14 @@ private Optional<String> optionalSetOrClearMethod(FieldDescriptorProto fieldDesc
}

String javaTypeName = getJavaTypeName(fieldDescriptor);
Map<?, ?> context = ImmutableMap.builder()
.put(METHOD_NAME, getJavaMethodName(fieldDescriptor))
.put(FIELD_TYPE, javaTypeName)
.put(OPTIONAL_CLASS, getOptionalClassName(javaTypeName))
.put(PRIMITIVE_OPTIONAL, isPrimitiveOptional(javaTypeName))
.put(OPTIONAL_GETTER_METHOD, getOptionalGetterMethod(javaTypeName))
.build();
Map<?, ?> context =
ImmutableMap.builder()
.put(METHOD_NAME, getJavaMethodName(fieldDescriptor))
.put(FIELD_TYPE, javaTypeName)
.put(OPTIONAL_CLASS, getOptionalClassName(javaTypeName))
.put(PRIMITIVE_OPTIONAL, isPrimitiveOptional(javaTypeName))
.put(OPTIONAL_GETTER_METHOD, getOptionalGetterMethod(javaTypeName))
.build();
return Optional.of(applyTemplate(templatePath("optionalSetOrClear.mustache"), context));
}

Expand All @@ -193,16 +228,34 @@ private Optional<String> optionalGet(FieldDescriptorProto fieldDescriptor) {
}

String javaTypeName = getJavaTypeName(fieldDescriptor);
Map<?, ?> context = ImmutableMap.builder()
.put(METHOD_NAME, getJavaMethodName(fieldDescriptor))
.put(FIELD_TYPE, javaTypeName)
.put(OPTIONAL_CLASS, getOptionalClassName(javaTypeName))
.put(PRIMITIVE_OPTIONAL, isPrimitiveOptional(javaTypeName))
.build();
Map<?, ?> context =
ImmutableMap.builder()
.put(METHOD_NAME, getJavaMethodName(fieldDescriptor))
.put(FIELD_TYPE, javaTypeName)
.put(OPTIONAL_CLASS, getOptionalClassName(javaTypeName))
.put(PRIMITIVE_OPTIONAL, isPrimitiveOptional(javaTypeName))
.build();
return Optional.of(applyTemplate(templatePath("optionalGet.mustache"), context));
}

private String getFileName(FileDescriptorProto fileDescriptor, DescriptorProto messageDescriptor) {
private Optional<String> optionalGetNullable(FieldDescriptorProto fieldDescriptor) {
if (!parameters.isGetterOptional()) {
return Optional.empty();
}

String javaTypeName = getJavaTypeName(fieldDescriptor);
Map<?, ?> context =
ImmutableMap.builder()
.put(METHOD_NAME, getJavaMethodName(fieldDescriptor))
.put(FIELD_TYPE, javaTypeName)
.put(OPTIONAL_CLASS, getOptionalClassName(javaTypeName))
.put(PRIMITIVE_OPTIONAL, isPrimitiveOptional(javaTypeName))
.build();
return Optional.of(applyTemplate(templatePath("optionalGetNullable.mustache"), context));
}

private String getFileName(
FileDescriptorProto fileDescriptor, DescriptorProto messageDescriptor) {
if (fileDescriptor.getOptions().getJavaMultipleFiles()) {
return messageDescriptor.getName();
}
Expand All @@ -214,11 +267,14 @@ private String getFileName(FileDescriptorProto fileDescriptor, DescriptorProto m
return Optional.ofNullable(protoTypeMap.toJavaTypeName(protoTypeName))
.map(javaType -> javaType.substring(0, javaType.lastIndexOf('.')))
.map(javaType -> javaType.substring(javaType.lastIndexOf('.') + 1))
.orElseThrow(() -> new IllegalArgumentException("Failed to find filename for proto '" + fileDescriptor.getName() + "'"));
.orElseThrow(
() ->
new IllegalArgumentException(
"Failed to find filename for proto '" + fileDescriptor.getName() + "'"));
}

private String getJavaMethodName(FieldDescriptorProto fieldDescriptor) {
return fieldDescriptor.getJsonName().substring(0, 1).toUpperCase(Locale.ROOT) + fieldDescriptor.getJsonName().substring(1);
return underscoresToCamelCase(fieldDescriptor.getName(), true, false);
}

private String getJavaTypeName(FieldDescriptorProto fieldDescriptor) {
Expand All @@ -228,10 +284,16 @@ private String getJavaTypeName(FieldDescriptorProto fieldDescriptor) {
.map(FieldDescriptor.Type::valueOf)
.map(FieldDescriptor.Type::getJavaType)
.map(PRIMITIVE_CLASSES::get)
.orElseThrow(() -> new IllegalArgumentException("Failed to find java type for field:\n" + fieldDescriptor));
.orElseThrow(
() ->
new IllegalArgumentException(
"Failed to find java type for field:\n" + fieldDescriptor));
}
return Optional.ofNullable(protoTypeMap.toJavaTypeName(protoTypeName))
.orElseThrow(() -> new IllegalArgumentException("Failed to find java type for prototype '" + protoTypeName + "'"));
.orElseThrow(
() ->
new IllegalArgumentException(
"Failed to find java type for prototype '" + protoTypeName + "'"));
}

private String getOptionalClassName(String javaTypeName) {
Expand All @@ -242,22 +304,72 @@ private String getOptionalClassName(String javaTypeName) {

private String getOptionalGetterMethod(String javaTypeName) {
return parameters.isUsePrimitiveOptionals()
? PRIMITIVE_OPTIONAL_GETTER_METHODS.getOrDefault(javaTypeName, DEFAULT_OPTIONAL_GETTER_METHOD)
? PRIMITIVE_OPTIONAL_GETTER_METHODS.getOrDefault(
javaTypeName, DEFAULT_OPTIONAL_GETTER_METHOD)
: DEFAULT_OPTIONAL_GETTER_METHOD;
}

private boolean isPrimitiveOptional(String javaTypeName) {
return parameters.isUsePrimitiveOptionals() && PRIMITIVE_OPTIONALS.containsKey(javaTypeName);
}

private static boolean hasFieldPresence(FieldDescriptorProto fieldDescriptor) {
return fieldDescriptor.getLabel() != Label.LABEL_REPEATED
&& (fieldDescriptor.getProto3Optional()
|| fieldDescriptor.getType() == Type.TYPE_MESSAGE
|| fieldDescriptor.hasOneofIndex());
}
private String underscoresToCamelCase(
String input, boolean capNextLetter, boolean preservePeriod) {
StringBuilder result = new StringBuilder();

private static String templatePath(String path) {
return TEMPLATES_DIRECTORY + path;
// Note: I distrust ctype.h due to locales.
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
if ('a' <= c && c <= 'z') {
if (capNextLetter) {
result.append((char) (c + ('A' - 'a')));
} else {
result.append(c);
}
capNextLetter = false;
} else if ('A' <= c && c <= 'Z') {
if (i == 0 && !capNextLetter) {
// Force first letter to lower-case unless explicitly told to
// capitalize it.
result.append((char) (c + ('a' - 'A')));
} else {
// Capital letters after the first are left as-is.
result.append(c);
}
capNextLetter = false;
} else if ('0' <= c && c <= '9') {
result.append(c);
capNextLetter = true;
} else {
capNextLetter = true;
if (c == '.' && preservePeriod) {
result.append(c);
}
}
}
// Add a trailing "_" if the name should be altered.
if (input.length() > 0 && input.charAt(input.length() - 1) == '#') {
result.append('_');
}

// https://github.com/protocolbuffers/protobuf/issues/8101
// To avoid generating invalid identifiers - if the input string
// starts with _<digit> (or multiple underscores then digit) then
// we need to preserve the underscore as an identifier cannot start
// with a digit.
// This check is being done after the loop rather than before
// to handle the case where there are multiple underscores before the
// first digit. We let them all be consumed so we can see if we would
// start with a digit.
// Note: not preserving leading underscores for all otherwise valid identifiers
// so as to not break anything that relies on the existing behaviour
if (result.length() > 0
&& '0' <= result.charAt(0)
&& result.charAt(0) <= '9'
&& input.length() > 0
&& input.charAt(0) == '_') {
result.insert(0, '_');
}
return result.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
public {{#primitiveOptional}}{{/primitiveOptional}}{{^primitiveOptional}}{{javaFieldType}}{{/primitiveOptional}} getOrNull{{javaMethodName}}() {
if (has{{javaMethodName}}()) {
return get{{javaMethodName}}();
} else {
return null;
}
}