All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertx.grpc.protoc.plugin.VertxGrpcGenerator Maven / Gradle / Ivy

There is a newer version: 5.0.0.CR1
Show newest version
package io.vertx.grpc.protoc.plugin;

import com.google.common.base.Strings;
import com.google.common.html.HtmlEscapers;
import com.google.protobuf.DescriptorProtos;
import com.google.protobuf.compiler.PluginProtos;
import com.salesforce.jprotoc.Generator;
import com.salesforce.jprotoc.GeneratorException;
import com.salesforce.jprotoc.ProtoTypeMap;
import com.salesforce.jprotoc.ProtocPlugin;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Rogelio Orts
 */
public class VertxGrpcGenerator extends Generator {

  private static final int SERVICE_NUMBER_OF_PATHS = 2;
  private static final int METHOD_NUMBER_OF_PATHS = 4;
  private static final String CLASS_PREFIX = "Vertx";

  private String getServiceJavaDocPrefix() {
    return "    ";
  }

  private String getMethodJavaDocPrefix() {
    return "        ";
  }

  @Override
  protected List supportedFeatures() {
    return Collections.singletonList(PluginProtos.CodeGeneratorResponse.Feature.FEATURE_PROTO3_OPTIONAL);
  }

  @Override
  public List generateFiles(PluginProtos.CodeGeneratorRequest request) throws GeneratorException {
    ProtoTypeMap typeMap = ProtoTypeMap.of(request.getProtoFileList());

    List protosToGenerate = request.getProtoFileList().stream()
      .filter(protoFile -> request.getFileToGenerateList().contains(protoFile.getName()))
      .collect(Collectors.toList());

    List services = findServices(protosToGenerate, typeMap);
    return generateFiles(services);
  }

  private List findServices(List protos, ProtoTypeMap typeMap) {
    List contexts = new ArrayList<>();

    protos.forEach(fileProto -> {
      for (int serviceNumber = 0; serviceNumber < fileProto.getServiceCount(); serviceNumber++) {
        ServiceContext serviceContext = buildServiceContext(
          fileProto.getService(serviceNumber),
          typeMap,
          fileProto.getSourceCodeInfo().getLocationList(),
          serviceNumber
        );
        serviceContext.protoName = fileProto.getName();
        serviceContext.packageName = extractPackageName(fileProto);
        contexts.add(serviceContext);
      }
    });

    return contexts;
  }

  private String extractPackageName(DescriptorProtos.FileDescriptorProto proto) {
    DescriptorProtos.FileOptions options = proto.getOptions();
    if (options != null) {
      String javaPackage = options.getJavaPackage();
      if (!Strings.isNullOrEmpty(javaPackage)) {
        return javaPackage;
      }
    }

    return Strings.nullToEmpty(proto.getPackage());
  }

  private ServiceContext buildServiceContext(DescriptorProtos.ServiceDescriptorProto serviceProto, ProtoTypeMap typeMap, List locations, int serviceNumber) {
    ServiceContext serviceContext = new ServiceContext();
    serviceContext.fileName = CLASS_PREFIX + serviceProto.getName() + "Grpc.java";
    serviceContext.className = CLASS_PREFIX + serviceProto.getName() + "Grpc";
    serviceContext.serviceName = serviceProto.getName();
    serviceContext.deprecated = serviceProto.getOptions() != null && serviceProto.getOptions().getDeprecated();

    List allLocationsForService = locations.stream()
      .filter(location ->
        location.getPathCount() >= 2 &&
          location.getPath(0) == DescriptorProtos.FileDescriptorProto.SERVICE_FIELD_NUMBER &&
          location.getPath(1) == serviceNumber
      )
      .collect(Collectors.toList());

    DescriptorProtos.SourceCodeInfo.Location serviceLocation = allLocationsForService.stream()
      .filter(location -> location.getPathCount() == SERVICE_NUMBER_OF_PATHS)
      .findFirst()
      .orElseGet(DescriptorProtos.SourceCodeInfo.Location::getDefaultInstance);
    serviceContext.javaDoc = getJavaDoc(getComments(serviceLocation), getServiceJavaDocPrefix());

    for (int methodNumber = 0; methodNumber < serviceProto.getMethodCount(); methodNumber++) {
      MethodContext methodContext = buildMethodContext(
        serviceProto.getMethod(methodNumber),
        typeMap,
        locations,
        methodNumber
      );

      serviceContext.methods.add(methodContext);
    }
    return serviceContext;
  }

  private MethodContext buildMethodContext(DescriptorProtos.MethodDescriptorProto methodProto, ProtoTypeMap typeMap, List locations, int methodNumber) {
    MethodContext methodContext = new MethodContext();
    methodContext.methodName = mixedLower(methodProto.getName());
    methodContext.inputType = typeMap.toJavaTypeName(methodProto.getInputType());
    methodContext.outputType = typeMap.toJavaTypeName(methodProto.getOutputType());
    methodContext.deprecated = methodProto.getOptions() != null && methodProto.getOptions().getDeprecated();
    methodContext.isManyInput = methodProto.getClientStreaming();
    methodContext.isManyOutput = methodProto.getServerStreaming();
    methodContext.methodNumber = methodNumber;

    DescriptorProtos.SourceCodeInfo.Location methodLocation = locations.stream()
      .filter(location ->
        location.getPathCount() == METHOD_NUMBER_OF_PATHS &&
          location.getPath(METHOD_NUMBER_OF_PATHS - 1) == methodNumber
      )
      .findFirst()
      .orElseGet(DescriptorProtos.SourceCodeInfo.Location::getDefaultInstance);
    methodContext.javaDoc = getJavaDoc(getComments(methodLocation), getMethodJavaDocPrefix());

    if (!methodProto.getClientStreaming() && !methodProto.getServerStreaming()) {
      methodContext.vertxCallsMethodName = "oneToOne";
      methodContext.grpcCallsMethodName = "asyncUnaryCall";
    }
    if (!methodProto.getClientStreaming() && methodProto.getServerStreaming()) {
      methodContext.vertxCallsMethodName = "oneToMany";
      methodContext.grpcCallsMethodName = "asyncServerStreamingCall";
    }
    if (methodProto.getClientStreaming() && !methodProto.getServerStreaming()) {
      methodContext.vertxCallsMethodName = "manyToOne";
      methodContext.grpcCallsMethodName = "asyncClientStreamingCall";
    }
    if (methodProto.getClientStreaming() && methodProto.getServerStreaming()) {
      methodContext.vertxCallsMethodName = "manyToMany";
      methodContext.grpcCallsMethodName = "asyncBidiStreamingCall";
    }
    return methodContext;
  }

  // java keywords from: https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.9
  private static final List JAVA_KEYWORDS = Arrays.asList(
    "abstract",
    "assert",
    "boolean",
    "break",
    "byte",
    "case",
    "catch",
    "char",
    "class",
    "const",
    "continue",
    "default",
    "do",
    "double",
    "else",
    "enum",
    "extends",
    "final",
    "finally",
    "float",
    "for",
    "goto",
    "if",
    "implements",
    "import",
    "instanceof",
    "int",
    "interface",
    "long",
    "native",
    "new",
    "package",
    "private",
    "protected",
    "public",
    "return",
    "short",
    "static",
    "strictfp",
    "super",
    "switch",
    "synchronized",
    "this",
    "throw",
    "throws",
    "transient",
    "try",
    "void",
    "volatile",
    "while",
    // additional ones added by us
    "true",
    "false"
  );

  /**
   * Adjust a method name prefix identifier to follow the JavaBean spec:
   * - decapitalize the first letter
   * - remove embedded underscores & capitalize the following letter
   * 

* Finally, if the result is a reserved java keyword, append an underscore. * * @param word method name * @return lower name */ private static String mixedLower(String word) { StringBuffer w = new StringBuffer(); w.append(Character.toLowerCase(word.charAt(0))); boolean afterUnderscore = false; for (int i = 1; i < word.length(); ++i) { char c = word.charAt(i); if (c == '_') { afterUnderscore = true; } else { if (afterUnderscore) { w.append(Character.toUpperCase(c)); } else { w.append(c); } afterUnderscore = false; } } if (JAVA_KEYWORDS.contains(w)) { w.append('_'); } return w.toString(); } private List generateFiles(List services) { return services.stream() .map(this::buildFile) .collect(Collectors.toList()); } private PluginProtos.CodeGeneratorResponse.File buildFile(ServiceContext context) { String content = applyTemplate("VertxStub.mustache", context); return PluginProtos.CodeGeneratorResponse.File .newBuilder() .setName(absoluteFileName(context)) .setContent(content) .build(); } private String absoluteFileName(ServiceContext ctx) { String dir = ctx.packageName.replace('.', '/'); if (Strings.isNullOrEmpty(dir)) { return ctx.fileName; } else { return dir + "/" + ctx.fileName; } } private String getComments(DescriptorProtos.SourceCodeInfo.Location location) { return location.getLeadingComments().isEmpty() ? location.getTrailingComments() : location.getLeadingComments(); } private String getJavaDoc(String comments, String prefix) { if (!comments.isEmpty()) { StringBuilder builder = new StringBuilder("/**\n") .append(prefix).append(" *

\n");
      Arrays.stream(HtmlEscapers.htmlEscaper().escape(comments).split("\n"))
        .map(line -> line.replace("*/", "*/").replace("*", "*"))
        .forEach(line -> builder.append(prefix).append(" * ").append(line).append("\n"));
      builder
        .append(prefix).append(" * 
\n") .append(prefix).append(" */"); return builder.toString(); } return null; } /** * Template class for proto Service objects. */ private static class ServiceContext { // CHECKSTYLE DISABLE VisibilityModifier FOR 8 LINES public String fileName; public String protoName; public String packageName; public String className; public String serviceName; public boolean deprecated; public String javaDoc; public final List methods = new ArrayList<>(); public List unaryUnaryMethods() { return methods.stream().filter(m -> !m.isManyInput && !m.isManyOutput).collect(Collectors.toList()); } public List unaryManyMethods() { return methods.stream().filter(m -> !m.isManyInput && m.isManyOutput).collect(Collectors.toList()); } public List manyUnaryMethods() { return methods.stream().filter(m -> m.isManyInput && !m.isManyOutput).collect(Collectors.toList()); } public List manyManyMethods() { return methods.stream().filter(m -> m.isManyInput && m.isManyOutput).collect(Collectors.toList()); } } /** * Template class for proto RPC objects. */ private static class MethodContext { // CHECKSTYLE DISABLE VisibilityModifier FOR 10 LINES public String methodName; public String inputType; public String outputType; public boolean deprecated; public boolean isManyInput; public boolean isManyOutput; public String vertxCallsMethodName; public String grpcCallsMethodName; public int methodNumber; public String javaDoc; // This method mimics the upper-casing method ogf gRPC to ensure compatibility // See https://github.com/grpc/grpc-java/blob/v1.8.0/compiler/src/java_plugin/cpp/java_generator.cpp#L58 public String methodNameUpperUnderscore() { StringBuilder s = new StringBuilder(); for (int i = 0; i < methodName.length(); i++) { char c = methodName.charAt(i); s.append(Character.toUpperCase(c)); if ((i < methodName.length() - 1) && Character.isLowerCase(c) && Character.isUpperCase(methodName.charAt(i + 1))) { s.append('_'); } } return s.toString(); } public String methodNameGetter() { return VertxGrpcGenerator.mixedLower("get_" + methodName + "_method"); } public String methodHeader() { String mh = ""; if (!Strings.isNullOrEmpty(javaDoc)) { mh = javaDoc; } if (deprecated) { mh += "\n @Deprecated"; } return mh; } } public static void main(String[] args) { if (args.length == 0) { ProtocPlugin.generate(new VertxGrpcGenerator()); } else { ProtocPlugin.debug(new VertxGrpcGenerator(), args[0]); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy