From 76a384f77efa27c18a9bfbcfee3d0088b08c10db Mon Sep 17 00:00:00 2001 From: godotg Date: Mon, 11 Dec 2023 18:14:39 +0800 Subject: [PATCH] feat[protobuf]: oneProtocol param, all protocol files are generated in a single protocol file --- .../generate/GenerateProtocolFile.java | 18 +++ .../serializer/protobuf/GeneratePbUtils.java | 151 +++++++++++++++++- .../protobuf/PbGenerateOperation.java | 14 +- .../com/zfoo/protocol/util/FileUtils.java | 1 + .../generate/GenerateProtobufTesting.java | 2 +- protocol/src/test/protobuf/all_type.proto | 5 +- 6 files changed, 180 insertions(+), 11 deletions(-) diff --git a/protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolFile.java b/protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolFile.java index 8ffd585c..f0efc51d 100644 --- a/protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolFile.java +++ b/protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolFile.java @@ -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; diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/protobuf/GeneratePbUtils.java b/protocol/src/main/java/com/zfoo/protocol/serializer/protobuf/GeneratePbUtils.java index 867b1ce5..448c94fb 100644 --- a/protocol/src/main/java/com/zfoo/protocol/serializer/protobuf/GeneratePbUtils.java +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/protobuf/GeneratePbUtils.java @@ -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 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 protos, Proto proto) { + var imports = new HashSet(); + 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 protos, Proto proto, String fieldType, Set 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())); + } } diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/protobuf/PbGenerateOperation.java b/protocol/src/main/java/com/zfoo/protocol/serializer/protobuf/PbGenerateOperation.java index 5ca8bf07..53e9678f 100644 --- a/protocol/src/main/java/com/zfoo/protocol/serializer/protobuf/PbGenerateOperation.java +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/protobuf/PbGenerateOperation.java @@ -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; + } } diff --git a/protocol/src/main/java/com/zfoo/protocol/util/FileUtils.java b/protocol/src/main/java/com/zfoo/protocol/util/FileUtils.java index 955d3cb1..8f59fbde 100644 --- a/protocol/src/main/java/com/zfoo/protocol/util/FileUtils.java +++ b/protocol/src/main/java/com/zfoo/protocol/util/FileUtils.java @@ -48,6 +48,7 @@ public abstract class FileUtils { * */ 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) diff --git a/protocol/src/test/java/com/zfoo/protocol/generate/GenerateProtobufTesting.java b/protocol/src/test/java/com/zfoo/protocol/generate/GenerateProtobufTesting.java index 2e97cb4f..efd5f3c4 100644 --- a/protocol/src/test/java/com/zfoo/protocol/generate/GenerateProtobufTesting.java +++ b/protocol/src/test/java/com/zfoo/protocol/generate/GenerateProtobufTesting.java @@ -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); } } diff --git a/protocol/src/test/protobuf/all_type.proto b/protocol/src/test/protobuf/all_type.proto index fc51ab45..620bfa2f 100644 --- a/protocol/src/test/protobuf/all_type.proto +++ b/protocol/src/test/protobuf/all_type.proto @@ -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 {