Skip to content
Merged
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
@@ -0,0 +1,5 @@
package com.salesforce.jprotoc;

public class JavaMethodStubTest {

}
2 changes: 1 addition & 1 deletion jprotoc-test/src/test/proto/helloworld.proto
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ service Greeter {
rpc SayHello (HelloRequest) returns (HelloResponse) {}

// Sends the current time
rpc SayTime (google.protobuf.Empty) returns (TimeResponse) {}
rpc SayTime (google.protobuf.Empty) returns (TimeResponse) {option deprecated=true;}

rpc SomeParam (my.someparameters.SomeParameter) returns (my.someparameters.SomeParameter) {}
}
Expand Down
107 changes: 75 additions & 32 deletions jprotoc/src/main/java/com/salesforce/jprotoc/jdk8/Jdk8Generator.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package com.salesforce.jprotoc.jdk8;

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;
Expand All @@ -16,6 +17,7 @@
import com.salesforce.jprotoc.ProtocPlugin;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
Expand All @@ -28,14 +30,17 @@ public static void main(String[] args) {

private static final String CLASS_SUFFIX = "Grpc8";

private static final String SERVICE_JAVADOC_PREFIX = " ";
private static final String METHOD_JAVADOC_PREFIX = " ";

@Override
public List<PluginProtos.CodeGeneratorResponse.File> generateFiles(PluginProtos.CodeGeneratorRequest request) throws GeneratorException {
final ProtoTypeMap protoTypeMap = ProtoTypeMap.of(request.getProtoFileList());
List<PluginProtos.CodeGeneratorResponse.File> files = new ArrayList<>();

for (DescriptorProtos.FileDescriptorProto protoFile : request.getProtoFileList()) {
if (request.getFileToGenerateList().contains(protoFile.getName())) {
for (Context ctx : extractContext(protoTypeMap, protoFile)) {
for (ServiceContext ctx : extractContext(protoTypeMap, protoFile)) {
files.add(buildFile(ctx));
}
}
Expand All @@ -44,17 +49,23 @@ public List<PluginProtos.CodeGeneratorResponse.File> generateFiles(PluginProtos.
return files;
}

private List<Context> extractContext(ProtoTypeMap protoTypeMap, DescriptorProtos.FileDescriptorProto proto) {
List<Context> contexts = new ArrayList<>();

for (DescriptorProtos.ServiceDescriptorProto service : proto.getServiceList()) {
Context ctx = extractServiceContext(protoTypeMap, service);
ctx.packageName = extractPackageName(proto);
ctx.protoName = proto.getName();
contexts.add(ctx);
}
private List<ServiceContext> extractContext(ProtoTypeMap protoTypeMap, DescriptorProtos.FileDescriptorProto fileProto) {
List<ServiceContext> serviceContexts = new ArrayList<>();

List<DescriptorProtos.SourceCodeInfo.Location> locations = fileProto.getSourceCodeInfo().getLocationList();
locations.stream()
.filter(location -> location.getPathCount() == 2 && location.getPath(0) == DescriptorProtos.FileDescriptorProto.SERVICE_FIELD_NUMBER)
.forEach(location -> {
int serviceNumber = location.getPath(1);
DescriptorProtos.ServiceDescriptorProto serviceProto = fileProto.getService(serviceNumber);
ServiceContext ctx = extractServiceContext(protoTypeMap, serviceProto, locations, serviceNumber);
ctx.packageName = extractPackageName(fileProto);
ctx.protoName = fileProto.getName();
ctx.javaDoc = getJavaDoc(getComments(location), SERVICE_JAVADOC_PREFIX);
serviceContexts.add(ctx);
});

return contexts;
return serviceContexts;
}

private String extractPackageName(DescriptorProtos.FileDescriptorProto proto) {
Expand All @@ -69,26 +80,38 @@ private String extractPackageName(DescriptorProtos.FileDescriptorProto proto) {
return Strings.nullToEmpty(proto.getPackage());
}

private Context extractServiceContext(
private static final int METHOD_NUMBER_OF_PATHS = 4;
private ServiceContext extractServiceContext(
ProtoTypeMap protoTypeMap,
DescriptorProtos.ServiceDescriptorProto serviceProto) {
Context ctx = new Context();
DescriptorProtos.ServiceDescriptorProto serviceProto,
List<DescriptorProtos.SourceCodeInfo.Location> locations,
int serviceNumber) {
ServiceContext ctx = new ServiceContext();
ctx.fileName = serviceProto.getName() + CLASS_SUFFIX + ".java";
ctx.className = serviceProto.getName() + CLASS_SUFFIX;
ctx.serviceName = serviceProto.getName();
ctx.deprecated = serviceProto.getOptions() != null && serviceProto.getOptions().getDeprecated();

// Identify methods to generate a CompletableFuture-based client for.
// Only unary methods are supported.
serviceProto.getMethodList().stream()
.filter(method -> !method.getClientStreaming() && !method.getServerStreaming())
.forEach(method -> {
ContextMethod ctxMethod = new ContextMethod();
ctxMethod.methodName = lowerCaseFirst(method.getName());
ctxMethod.inputType = protoTypeMap.toJavaTypeName(method.getInputType());
ctxMethod.outputType = protoTypeMap.toJavaTypeName(method.getOutputType());
ctxMethod.deprecated = method.getOptions() != null && method.getOptions().getDeprecated();
ctx.methods.add(ctxMethod);
locations.stream()
.filter(location -> location.getPathCount() == METHOD_NUMBER_OF_PATHS &&
location.getPath(0) == DescriptorProtos.FileDescriptorProto.SERVICE_FIELD_NUMBER &&
location.getPath(1) == serviceNumber &&
location.getPath(2) == DescriptorProtos.ServiceDescriptorProto.METHOD_FIELD_NUMBER)
.forEach(location -> {
int methodNumber = location.getPath(METHOD_NUMBER_OF_PATHS - 1);
DescriptorProtos.MethodDescriptorProto methodProto = serviceProto.getMethod(methodNumber);

// Identify methods to generate a CompletableFuture-based client for.
// Only unary methods are supported.
if (!methodProto.getClientStreaming() && !methodProto.getServerStreaming()) {
MethodContext ctxMethod = new MethodContext();
ctxMethod.methodName = lowerCaseFirst(methodProto.getName());
ctxMethod.inputType = protoTypeMap.toJavaTypeName(methodProto.getInputType());
ctxMethod.outputType = protoTypeMap.toJavaTypeName(methodProto.getOutputType());
ctxMethod.deprecated = methodProto.getOptions() != null && methodProto.getOptions().getDeprecated();
ctxMethod.javaDoc = getJavaDoc(getComments(location), METHOD_JAVADOC_PREFIX);
ctx.methods.add(ctxMethod);
}
});
return ctx;
}
Expand All @@ -97,7 +120,7 @@ private String lowerCaseFirst(String s) {
return Character.toLowerCase(s.charAt(0)) + s.substring(1);
}

private String absoluteFileName(Context ctx) {
private String absoluteFileName(ServiceContext ctx) {
String dir = ctx.packageName.replace('.', '/');
if (Strings.isNullOrEmpty(dir)) {
return ctx.fileName;
Expand All @@ -106,7 +129,7 @@ private String absoluteFileName(Context ctx) {
}
}

private PluginProtos.CodeGeneratorResponse.File buildFile(Context context) {
private PluginProtos.CodeGeneratorResponse.File buildFile(ServiceContext context) {
String content = applyTemplate("Jdk8Stub.mustache", context);
return PluginProtos.CodeGeneratorResponse.File
.newBuilder()
Expand All @@ -115,28 +138,48 @@ private PluginProtos.CodeGeneratorResponse.File buildFile(Context context) {
.build();
}

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(" * <pre>\n");
Arrays.stream(HtmlEscapers.htmlEscaper().escape(comments).split("\n"))
.forEach(line -> builder.append(prefix).append(" * ").append(line).append("\n"));
builder
.append(prefix).append(" * <pre>\n")
.append(prefix).append(" */");
return builder.toString();
}
return null;
}

/**
* Backing class for mustache template.
*/
private class Context {
// CHECKSTYLE DISABLE VisibilityModifier FOR 7 LINES
private 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 final List<ContextMethod> methods = new ArrayList<>();
public String javaDoc;
public final List<MethodContext> methods = new ArrayList<>();
}

/**
* Backing class for mustache template.
*/
private class ContextMethod {
// CHECKSTYLE DISABLE VisibilityModifier FOR 4 LINES
private class MethodContext {
// CHECKSTYLE DISABLE VisibilityModifier FOR 5 LINES
public String methodName;
public String inputType;
public String outputType;
public boolean deprecated;
public String javaDoc;
}
}
36 changes: 13 additions & 23 deletions jprotoc/src/main/resources/Jdk8Stub.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,20 @@ public class {{className}} {
/**
* Creates a new CompletableFuture-style stub that supports unary calls on the service
*/
@SuppressWarnings("unchecked")
public static {{serviceName}}CompletableFutureStub newCompletableFutureStub(Channel channel) {
return newCompletableFutureStub(channel, MoreFutures::toCompletableFuture);
}

/**
* Creates a new CompletableFuture-style stub that supports unary calls on the service
*/
@SuppressWarnings("unchecked")
public static {{serviceName}}CompletableFutureStub newCompletableFutureStub(Channel channel, Function<ListenableFuture, CompletableFuture> futureAdapter) {
return new {{serviceName}}CompletableFutureStub(channel, futureAdapter);
}

{{#javaDoc}}{{{javaDoc}}}{{/javaDoc}}
public static final class {{serviceName}}CompletableFutureStub extends AbstractStub<{{serviceName}}CompletableFutureStub> {
private {{serviceName}}Grpc.{{serviceName}}FutureStub innerStub;
private Function<ListenableFuture, CompletableFuture> futureAdapter;
Expand All @@ -48,39 +51,26 @@ public class {{className}} {
private {{serviceName}}CompletableFutureStub(Channel channel, CallOptions callOptions, Function<ListenableFuture, CompletableFuture> futureAdapter) {
super(channel, callOptions);
this.futureAdapter = futureAdapter;
innerStub = {{serviceName}}Grpc.newFutureStub(channel);
innerStub = {{serviceName}}Grpc.newFutureStub(channel).build(channel, callOptions);
}

@Override
protected {{serviceName}}CompletableFutureStub build(Channel channel, CallOptions callOptions) {
{{serviceName}}CompletableFutureStub stub = new {{serviceName}}CompletableFutureStub(channel, callOptions, futureAdapter);
forceCallOptions(stub.innerStub, callOptions);
return stub;
}
{{#methods}}{{#deprecated}}@Deprecated{{/deprecated}}
{{#methods}}

{{#javaDoc}}
{{{javaDoc}}}
{{/javaDoc}}
{{#deprecated}}
@java.lang.Deprecated
{{/deprecated}}
@SuppressWarnings("unchecked")
public CompletableFuture<{{outputType}}> {{methodName}}({{inputType}} request) {
return futureAdapter.apply(innerStub.{{methodName}}(request));
}
{{/methods}}

private void forceCallOptions({{serviceName}}Grpc.{{serviceName}}FutureStub stub, CallOptions options) {
try {
callOptionsField.set(stub, options);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}

private static final Field callOptionsField = getWritableCallOptonsField();

private static Field getWritableCallOptonsField() {
try {
Field callOptionsField = AbstractStub.class.getDeclaredField("callOptions");
callOptionsField.setAccessible(true);
return callOptionsField;
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
}
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<compilerArgument>-Xlint:unchecked</compilerArgument>
</configuration>
</plugin>

Expand Down