Skip to content

Commit

Permalink
Merge pull request #4 from github/gorzell/multi-file-java
Browse files Browse the repository at this point in the history
Fix a number of cases that were not handled in the TypeMapper.
  • Loading branch information
gorzell authored Mar 4, 2019
2 parents 22f2913 + 9e35542 commit 8484dab
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 35 deletions.
2 changes: 1 addition & 1 deletion plugin/gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=1.0.0
version=1.1.0
95 changes: 61 additions & 34 deletions plugin/src/main/java/com/flit/protoc/gen/server/TypeMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

import com.google.protobuf.DescriptorProtos;
import com.squareup.javapoet.ClassName;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TypeMapper {

// holds the qualified class name to the short class reference
// Maps the protobuf package and name to the fully qualified name of the generated Java class.
private final Map<String, String> mapping = new HashMap<>();

public TypeMapper() {
Expand All @@ -22,57 +21,85 @@ public TypeMapper(List<DescriptorProtos.FileDescriptorProto> files) {

public void add(DescriptorProtos.FileDescriptorProto proto) {
proto.getMessageTypeList().forEach(m -> {
mapping.put("." + proto.getPackage() + "." + m.getName(), getClassname(proto) + "." + m.getName());
mapping.put("." + proto.getPackage() + "." + m.getName(),
getOuterClassOrPackageName(proto) + "." + m.getName());
});
}

public ClassName get(String protobufFqcn) {
return ClassName.bestGuess(mapping.get(protobufFqcn));
}

public static String getClassname(DescriptorProtos.FileDescriptorProto proto) {
String clazz = proto.getOptions().getJavaOuterClassname();
/**
* Determine where message or service in a given proto file will be generated. Depending on the
* java specific options in the spec, this could be either inside of an outer class, or at the top
* level of the package.
*/
public static String getOuterClassOrPackageName(DescriptorProtos.FileDescriptorProto proto) {
// If no 'java_package' option is provided, the protoc compiler will default to the protobuf
// package name.
String packageName = proto.getOptions().hasJavaPackage() ?
proto.getOptions().getJavaPackage() : proto.getPackage();

// If this option is enabled protoc will generate a class for each message/service at the top
// level of the given package space. Because message name is appended in the add method, this
// should just return the package in that case. If there are collisions protoc should give a
// warning/error.
if (proto.getOptions().getJavaMultipleFiles()) {
return packageName;
}

if (clazz == null || clazz.isEmpty()) {
// If an outer class name is provided it should be used, otherwise we need to infer one based
// on the same rules the protoc compiler uses.
String outerClass = proto.getOptions().hasJavaOuterClassname() ?
proto.getOptions().getJavaOuterClassname() : outerClassNameFromProtoName(proto);

String basename = new File(proto.getName()).getName();
char[] classname = basename.substring(0, basename.lastIndexOf('.')).toCharArray();
StringBuilder sb = new StringBuilder();
if (outerClass.isEmpty()) {
throw new IllegalArgumentException("'option java_outer_classname' cannot be set to \"\".");
}

char previous = '_';
for (char c : classname) {
if (c == '_') {
previous = c;
continue;
}
String fqName = String.join(".", packageName, outerClass);

if (previous == '_') {
sb.append(Character.toUpperCase(c));
} else {
sb.append(c);
}
// check to see if there are any messages with this same class name as per java proto specs
// note that we also check the services too as the protoc compiler does that as well.
for (DescriptorProtos.DescriptorProto type : proto.getMessageTypeList()) {
if (type.getName().equals(outerClass)) {
return fqName + "OuterClass";
}
}

previous = c;
for (DescriptorProtos.ServiceDescriptorProto service : proto.getServiceList()) {
if (service.getName().equals(outerClass)) {
return fqName + "OuterClass";
}
}

clazz = sb.toString();
return fqName;
}

private static String outerClassNameFromProtoName(DescriptorProtos.FileDescriptorProto proto) {
String basename = new File(proto.getName()).getName();
char[] classname = basename.substring(0, basename.lastIndexOf('.')).toCharArray();
StringBuilder sb = new StringBuilder();

// check to see if there are any messages with this same class name as per java proto specs
// note that we also check the services too as the protoc compiler does that as well
for (DescriptorProtos.DescriptorProto type : proto.getMessageTypeList()) {
if (type.getName().equals(clazz)) {
return clazz + "OuterClass";
}
char previous = '_';
for (char c : classname) {
if (c == '_') {
previous = c;
continue;
}

for (DescriptorProtos.ServiceDescriptorProto service : proto.getServiceList()) {
if (service.getName().equals(clazz)) {
return clazz + "OuterClass";
}
if (previous == '_') {
sb.append(Character.toUpperCase(c));
} else {
sb.append(c);
}

previous = c;
}

return String.join(".", proto.getOptions().getJavaPackage(), clazz);
}
String clazz = sb.toString();

return clazz;
}
}
206 changes: 206 additions & 0 deletions plugin/src/test/java/com/flit/protoc/gen/server/TypeMapperTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package com.flit.protoc.gen.server;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import com.google.protobuf.DescriptorProtos;
import com.squareup.javapoet.ClassName;
import org.junit.Test;

public class TypeMapperTest {

private static final String PROTO_PACKAGE = "flit.test";
private static final String JAVA_PACKAGE = "com.flit.test";

private static final DescriptorProtos.DescriptorProto MAP_MESSAGE = DescriptorProtos.DescriptorProto
.newBuilder()
.setName("Map")
.build();

private static final DescriptorProtos.DescriptorProto MAPPER_MESSAGE = DescriptorProtos.DescriptorProto
.newBuilder()
.setName("Mapper")
.build();

@Test
public void protoPackage() {
DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder()
.setJavaMultipleFiles(false)
.build();

DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder()
.setPackage(PROTO_PACKAGE)
.setName("mapper.proto")
.setOptions(options)
.addMessageType(MAP_MESSAGE)
.build();

TypeMapper mapper = new TypeMapper();
mapper.add(proto);

ClassName result = mapper.get(".flit.test.Map");
assertEquals(PROTO_PACKAGE, result.packageName());
assertEquals("Mapper", result.enclosingClassName().simpleName());
assertEquals("Map", result.simpleName());
}

@Test
public void protoPackageNameCollision() {
DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder()
.setJavaMultipleFiles(false)
.build();

DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder()
.setPackage(PROTO_PACKAGE)
.setName("mapper.proto")
.setOptions(options)
.addMessageType(MAPPER_MESSAGE)
.build();

TypeMapper mapper = new TypeMapper();
mapper.add(proto);

ClassName result = mapper.get(".flit.test.Mapper");
assertEquals(PROTO_PACKAGE, result.packageName());
assertEquals("MapperOuterClass", result.enclosingClassName().simpleName());
assertEquals("Mapper", result.simpleName());
}

@Test
public void protoPackageWithOuterClass() {
DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder()
.setJavaMultipleFiles(false)
.setJavaOuterClassname("Mapper")
.build();

DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder()
.setPackage(PROTO_PACKAGE)
.setName("mapper.proto")
.setOptions(options)
.addMessageType(MAP_MESSAGE)
.build();

TypeMapper mapper = new TypeMapper();
mapper.add(proto);

ClassName result = mapper.get(".flit.test.Map");
assertEquals(PROTO_PACKAGE, result.packageName());
assertEquals("Mapper", result.enclosingClassName().simpleName());
assertEquals("Map", result.simpleName());
}

@Test
public void javaPackage() {
DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder()
.setJavaMultipleFiles(false)
.setJavaPackage(JAVA_PACKAGE)
.build();

DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder()
.setPackage(PROTO_PACKAGE)
.setName("mapper.proto")
.setOptions(options)
.addMessageType(MAP_MESSAGE)
.build();

TypeMapper mapper = new TypeMapper();
mapper.add(proto);

ClassName result = mapper.get(".flit.test.Map");
assertEquals(JAVA_PACKAGE, result.packageName());
assertEquals("Mapper", result.enclosingClassName().simpleName());
assertEquals("Map", result.simpleName());
}

@Test
public void javaPackageNameCollision() {
DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder()
.setJavaMultipleFiles(false)
.setJavaPackage(JAVA_PACKAGE)
.build();

DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder()
.setPackage(PROTO_PACKAGE)
.setName("mapper.proto")
.setOptions(options)
.addMessageType(MAPPER_MESSAGE)
.build();

TypeMapper mapper = new TypeMapper();
mapper.add(proto);

ClassName result = mapper.get(".flit.test.Mapper");
assertEquals(JAVA_PACKAGE, result.packageName());
assertEquals("MapperOuterClass", result.enclosingClassName().simpleName());
assertEquals("Mapper", result.simpleName());
}

@Test
public void javaPackageWithOuterClass() {
DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder()
.setJavaMultipleFiles(false)
.setJavaOuterClassname("Mapper")
.setJavaPackage(JAVA_PACKAGE)
.build();

DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder()
.setPackage(PROTO_PACKAGE)
.setName("mapper.proto")
.setOptions(options)
.addMessageType(MAP_MESSAGE)
.build();

TypeMapper mapper = new TypeMapper();
mapper.add(proto);

ClassName result = mapper.get(".flit.test.Map");
assertEquals(JAVA_PACKAGE, result.packageName());
assertEquals("Mapper", result.enclosingClassName().simpleName());
assertEquals("Map", result.simpleName());
}

@Test(expected = IllegalArgumentException.class)
public void javaPackageWithOuterClassEmpty() {
DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder()
.setJavaMultipleFiles(false)
.setJavaOuterClassname("")
.setJavaPackage(JAVA_PACKAGE)
.build();

DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder()
.setPackage(PROTO_PACKAGE)
.setName("mapper.proto")
.setOptions(options)
.addMessageType(MAP_MESSAGE)
.build();

TypeMapper mapper = new TypeMapper();
mapper.add(proto);

mapper.get(".flit.test.Map");
}

@Test
public void javaPackageWithOuterClassMultiFile() {
DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder()
.setJavaMultipleFiles(true)
.setJavaOuterClassname("Mapper")
.setJavaPackage(JAVA_PACKAGE)
.build();

DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder()
.setPackage(PROTO_PACKAGE)
.setName("mapper.proto")
.setOptions(options)
.addMessageType(MAP_MESSAGE)
.build();

TypeMapper mapper = new TypeMapper();
mapper.add(proto);

ClassName result = mapper.get(".flit.test.Map");
assertEquals(JAVA_PACKAGE, result.packageName());
assertNull(result.enclosingClassName());
assertEquals("Map", result.simpleName());
}
}
1 change: 1 addition & 0 deletions runtime/core/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
version=1.0.0

0 comments on commit 8484dab

Please sign in to comment.