From e3be35ecdaa7c643e8bcdd1e770eb727104e3eba Mon Sep 17 00:00:00 2001 From: Zixing Guo Date: Fri, 28 Jul 2023 16:14:28 +0800 Subject: [PATCH] fix naming conversion and add null get --- .../protoc/plugin/OptionalGenerator.java | 264 +++++++++++++----- .../templates/optionalGetNullable.mustache | 7 + 2 files changed, 195 insertions(+), 76 deletions(-) create mode 100644 protoc-gen-java-optional/src/main/resources/templates/optionalGetNullable.mustache diff --git a/protoc-gen-java-optional/src/main/java/org/grpcmock/protoc/plugin/OptionalGenerator.java b/protoc-gen-java-optional/src/main/java/org/grpcmock/protoc/plugin/OptionalGenerator.java index 99bbd30..f972414 100644 --- a/protoc-gen-java-optional/src/main/java/org/grpcmock/protoc/plugin/OptionalGenerator.java +++ b/protoc-gen-java-optional/src/main/java/org/grpcmock/protoc/plugin/OptionalGenerator.java @@ -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; @@ -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 PRIMITIVE_CLASSES = ImmutableMap.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 PRIMITIVE_OPTIONALS = ImmutableMap.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 PRIMITIVE_OPTIONAL_GETTER_METHODS = ImmutableMap.builder() - .put(Integer.class.getSimpleName(), "getAsInt") - .put(Long.class.getSimpleName(), "getAsLong") - .put(Double.class.getSimpleName(), "getAsDouble") - .build(); + private static final Map PRIMITIVE_CLASSES = + ImmutableMap.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 PRIMITIVE_OPTIONALS = + ImmutableMap.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 PRIMITIVE_OPTIONAL_GETTER_METHODS = + ImmutableMap.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 generateFiles(CodeGeneratorRequest request) throws GeneratorException { @@ -89,21 +101,25 @@ protected List supportedFeatures() { private Stream 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 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(); @@ -112,13 +128,22 @@ private Stream 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 handleSingleMessage(DescriptorProto messageDescriptor, String filePath, String fullMethodName) { + private Stream 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); } @@ -128,24 +153,29 @@ private Optional createFile( String fileName, String fullMethodName, String scopeType, - Function> createMethods - ) { + Function> 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 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)); @@ -155,7 +185,10 @@ private Optional createBuilderMethods(FieldDescriptorProto fieldDescript private Optional 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(); } @@ -164,10 +197,11 @@ private Optional 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)); } @@ -177,13 +211,14 @@ private Optional 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)); } @@ -193,16 +228,34 @@ private Optional 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 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(); } @@ -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) { @@ -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) { @@ -242,7 +304,8 @@ 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; } @@ -250,14 +313,63 @@ 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 _ (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(); } } diff --git a/protoc-gen-java-optional/src/main/resources/templates/optionalGetNullable.mustache b/protoc-gen-java-optional/src/main/resources/templates/optionalGetNullable.mustache new file mode 100644 index 0000000..04c8b52 --- /dev/null +++ b/protoc-gen-java-optional/src/main/resources/templates/optionalGetNullable.mustache @@ -0,0 +1,7 @@ +public {{#primitiveOptional}}{{/primitiveOptional}}{{^primitiveOptional}}{{javaFieldType}}{{/primitiveOptional}} getOrNull{{javaMethodName}}() { + if (has{{javaMethodName}}()) { + return get{{javaMethodName}}(); + } else { + return null; + } +}