feat[protobuf]: oneProtocol param, all protocol files are generated in a single protocol file

This commit is contained in:
godotg
2023-12-11 18:14:39 +08:00
parent c604a15680
commit 76a384f77e
6 changed files with 180 additions and 11 deletions
@@ -26,6 +26,8 @@ import com.zfoo.protocol.serializer.javascript.GenerateJsUtils;
import com.zfoo.protocol.serializer.lua.GenerateLuaUtils;
import com.zfoo.protocol.serializer.python.GeneratePyUtils;
import com.zfoo.protocol.serializer.typescript.GenerateTsUtils;
import com.zfoo.protocol.util.FileUtils;
import com.zfoo.protocol.util.StringUtils;
import java.io.IOException;
import java.lang.reflect.Field;
@@ -61,6 +63,22 @@ public abstract class GenerateProtocolFile {
return builder;
}
/**
* 给每行新增若干Tab
*/
public static String addTabs(String str, int deep) {
if (StringUtils.isEmpty(str)) {
return str;
}
var splits = str.split(FileUtils.LS_REGEX);
var builder = new StringBuilder();
for (var split : splits) {
builder.append(TAB.repeat(Math.max(0, deep)));
builder.append(split).append(FileUtils.LS);
}
return builder.toString();
}
public static void clear() {
generateProtocolFilter = null;
index = null;
@@ -17,6 +17,7 @@ import com.zfoo.protocol.anno.Compatible;
import com.zfoo.protocol.anno.Note;
import com.zfoo.protocol.anno.Protocol;
import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.generate.GenerateProtocolFile;
import com.zfoo.protocol.model.Pair;
import com.zfoo.protocol.serializer.protobuf.parser.Proto;
import com.zfoo.protocol.serializer.protobuf.parser.ProtoParser;
@@ -117,10 +118,40 @@ public abstract class GeneratePbUtils {
if (CollectionUtils.isEmpty(pbMessages)) {
continue;
}
for (var pbMessage : pbMessages) {
var code = buildMessage(pbGenerateOperation, protos, proto, pbMessage);
var filePath = StringUtils.format("{}/{}/{}.java", messageOutputPath, proto.getName(), pbMessage.getName());
FileUtils.writeStringToFile(new File(filePath), code, false);
if (pbGenerateOperation.isOneProtocol()) {
var builder = new StringBuilder();
var outClassName = toOutClassName(proto);
// import other class
var imports = buildOneProtocolMessageImports(pbGenerateOperation, protos, proto);
builder.append(imports);
// out class builder
var protoComment = buildProtoComment(proto);
builder.append(protoComment);
builder.append(StringUtils.format("public class {} {", outClassName)).append(LS);
// inner class builder
for (var pbMessage : pbMessages) {
// document
var documentComment = buildDocumentComment(pbMessage);
builder.append(GenerateProtocolFile.addTabs(documentComment, 1));
// message
if (pbGenerateOperation.isRecordClass()) {
var recordBody = buildRecordBody(pbMessage);
builder.append(GenerateProtocolFile.addTabs(recordBody, 1));
} else {
var classBody = buildClassBody(pbMessage);
classBody = classBody.replaceFirst("public class ", "public static class ");
builder.append(GenerateProtocolFile.addTabs(classBody, 1));
}
}
builder.append("}");
var filePath = StringUtils.format("{}/{}.java", messageOutputPath, outClassName);
FileUtils.writeStringToFile(new File(filePath), builder.toString(), false);
} else {
for (var pbMessage : pbMessages) {
var code = buildMessage(pbGenerateOperation, protos, proto, pbMessage);
var filePath = StringUtils.format("{}/{}/{}.java", messageOutputPath, proto.getName(), pbMessage.getName());
FileUtils.writeStringToFile(new File(filePath), code, false);
}
}
}
}
@@ -165,6 +196,10 @@ public abstract class GeneratePbUtils {
// -----------------------------------------------------------------------------------------------------------------
private static boolean isCompatiblePbField(PbField pbField) {
return pbField.getTag() >= COMPATIBLE_FIELD_TAG;
}
public static String buildMessage(PbGenerateOperation pbGenerateOperation, List<Proto> protos, Proto proto, PbMessage pbMessage) {
var builder = new StringBuilder();
@@ -202,7 +237,7 @@ public abstract class GeneratePbUtils {
imports.add(Note.class.getName());
}
if (pbField.getTag() >= COMPATIBLE_FIELD_TAG) {
if (isCompatiblePbField(pbField)) {
imports.add(Compatible.class.getName());
}
@@ -257,6 +292,16 @@ public abstract class GeneratePbUtils {
throw new RuntimeException(StringUtils.format("not found type:[{}] in proto:[{}]", fieldType, proto.getName()));
}
private static String buildProtoComment(Proto proto) {
if (CollectionUtils.isEmpty(proto.getComments())) {
return StringUtils.EMPTY;
}
var builder = new StringBuilder();
builder.append("/**").append(LS);
proto.getComments().forEach(it -> builder.append(StringUtils.format(" * {}", it)).append(LS));
builder.append(" */").append(LS);
return builder.toString();
}
private static String buildDocumentComment(PbMessage msg) {
if (CollectionUtils.isEmpty(msg.getComments())) {
@@ -311,7 +356,7 @@ public abstract class GeneratePbUtils {
var fieldComment = buildFieldComment(pbField);
builder.append(fieldComment);
if (pbField.getTag() >= COMPATIBLE_FIELD_TAG) {
if (isCompatiblePbField(pbField)) {
var tag = pbField.getTag() - COMPATIBLE_FIELD_TAG;
builder.append(TAB).append(StringUtils.format("@Compatible({})", tag)).append(LS);
}
@@ -343,7 +388,7 @@ public abstract class GeneratePbUtils {
var fieldComment = buildFieldComment(pbField);
builder.append(fieldComment);
if (pbField.getTag() >= COMPATIBLE_FIELD_TAG) {
if (isCompatiblePbField(pbField)) {
var tag = pbField.getTag() - COMPATIBLE_FIELD_TAG;
builder.append(TAB).append(StringUtils.format("@Compatible({})", tag)).append(LS);
}
@@ -370,4 +415,96 @@ public abstract class GeneratePbUtils {
builder.append("}");
return builder.toString();
}
// -----------------------------------------------------------------------------------------------------------------
private static String toOutClassName(Proto proto) {
var protoName = proto.getName();
var splits = protoName.split("[-_]");
var builder = new StringBuilder();
for (var split : splits) {
if (StringUtils.isBlank(split)) {
continue;
}
// 首字母大写
builder.append(StringUtils.capitalize(split.trim()));
}
var outClassName = builder.toString();
for (var pbMessage : proto.getPbMessages()) {
if (pbMessage.getName().equals(outClassName)) {
builder.append("s");
}
}
return builder.toString();
}
private static String buildOneProtocolMessageImports(PbGenerateOperation pbGenerateOperation, List<Proto> protos, Proto proto) {
var imports = new HashSet<String>();
imports.add(Protocol.class.getName());
for (var pbMessage : proto.getPbMessages()) {
var pbFields = pbMessage.getFields();
if (CollectionUtils.isEmpty(pbFields)) {
return StringUtils.EMPTY;
}
for (var pbField : pbFields) {
if (CollectionUtils.isNotEmpty(pbField.getComments())) {
imports.add(Note.class.getName());
}
if (isCompatiblePbField(pbField)) {
imports.add(Compatible.class.getName());
}
if (pbField instanceof PbMapField) {
imports.add(Map.class.getName());
var pbMapField = (PbMapField) pbField;
buildOneProtocolImports(pbGenerateOperation, protos, proto, pbMapField.getKey().value(), imports);
buildOneProtocolImports(pbGenerateOperation, protos, proto, pbMapField.getValue(), imports);
continue;
}
if (pbField.getCardinality() == PbField.Cardinality.REPEATED) {
imports.add(List.class.getName());
}
buildOneProtocolImports(pbGenerateOperation, protos, proto, pbField.getType(), imports);
}
}
var builder = new StringBuilder();
imports.stream()
.sorted(Comparator.naturalOrder())
.forEach(it -> builder.append(StringUtils.format("import {};", it)).append(LS));
return builder.toString();
}
private static void buildOneProtocolImports(PbGenerateOperation pbGenerateOperation, List<Proto> protos, Proto proto, String fieldType, Set<String> imports) {
// 基本数据类型不需要导入
var typeProtobuf = PbType.typeOfProtobuf(fieldType);
if (typeProtobuf != null) {
return;
}
// 属于同一个包不需要导入
if (proto.getPbMessages().stream().anyMatch(it -> it.getName().equals(fieldType))) {
return;
}
// 遍历其它的proto找到需要导入的类
for (var pt : protos) {
for (var msg : pt.getPbMessages()) {
if (msg.getName().equals(fieldType)) {
if (StringUtils.isBlank(pbGenerateOperation.getJavaPackage())) {
imports.add(StringUtils.format("static {}.*", toOutClassName(pt)));
} else {
imports.add(StringUtils.format("static {}.{}.*", pbGenerateOperation.getJavaPackage(), toOutClassName(pt)));
}
return;
}
}
}
throw new RuntimeException(StringUtils.format("not found type:[{}] in proto:[{}]", fieldType, proto.getName()));
}
}
@@ -21,7 +21,11 @@ public class PbGenerateOperation {
/**
* Whether generated class is record
*/
private boolean recordClass = false;
private boolean recordClass;
/**
* All protocol files are generated in a single protocol file.
*/
private boolean oneProtocol;
public String getProtoPath() {
return protoPath;
@@ -54,4 +58,12 @@ public class PbGenerateOperation {
public void setRecordClass(boolean recordClass) {
this.recordClass = recordClass;
}
public boolean isOneProtocol() {
return oneProtocol;
}
public void setOneProtocol(boolean oneProtocol) {
this.oneProtocol = oneProtocol;
}
}
@@ -48,6 +48,7 @@ public abstract class FileUtils {
* </pre>
*/
public static final String LS = System.lineSeparator();
public static final String LS_REGEX = "\\r?\\n";
public static final String UNIX_LS = "\\n";
public static final String WINDOWS_LS = "\\r\\n";
// The file copy buffer size (30 MB)
@@ -28,7 +28,7 @@ public class GenerateProtobufTesting {
buildOption.setOutputPath("D:\\github\\zfoo\\protocol\\src\\test\\zfoopb");
buildOption.setJavaPackage("com.zfoo.protocol.generate.test");
// buildOption.setRecordClass(true);
// buildOption.setOneProtocol(true);
GeneratePbUtils.create(buildOption);
}
}
+3 -2
View File
@@ -1,12 +1,13 @@
syntax = "proto3";
// start_protocol_id = 100
package test.message;
// 这个是整个proto的注释
import "one_map.proto";
import "one_message.proto";
option java_package = "start";
// start_protocol_id = 100
// 如果protobuf的字段的tag超过1000,则视这个字段为需要兼容的协议字段
message AllType {