chore: import upstream snapshot with attribution
CI / unit-test (push) Has been cancelled
CI / detect-changes (push) Has been cancelled
CI / build (push) Has been cancelled
Publish docs via GitHub Pages / Deploy docs (push) Has been cancelled
CI / test-harness (push) Has been cancelled
CI / generate-e2e-matrix (push) Has been cancelled
CI / e2e (push) Has been cancelled
CI / build-ui (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
UI v2 Integration CI / E2E (Integration) (push) Has been cancelled
UI v2 CI / Lint, Format & Test (push) Has been cancelled
UI v2 CI / E2E (Mocked) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:37:56 +08:00
commit a9cd7750f4
3727 changed files with 627706 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
Annotation processor is used to generate protobuf files from the annotations.
+24
View File
@@ -0,0 +1,24 @@
sourceSets {
example
}
dependencies {
implementation project(':conductor-annotations')
api 'com.google.guava:guava:33.5.0-jre'
api 'com.squareup:javapoet:1.13.0'
api 'com.github.jknack:handlebars:4.5.0'
api "com.google.protobuf:protobuf-java:${revProtoBuf}"
api 'jakarta.annotation:jakarta.annotation-api:2.1.1'
api gradleApi()
exampleImplementation sourceSets.main.output
exampleImplementation project(':conductor-annotations')
}
task exampleJar(type: Jar) {
archiveFileName = 'example.jar'
from sourceSets.example.output.classesDirs
}
testClasses.finalizedBy(exampleJar)
@@ -0,0 +1,25 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.example;
import com.netflix.conductor.annotations.protogen.ProtoField;
import com.netflix.conductor.annotations.protogen.ProtoMessage;
@ProtoMessage
public class Example {
@ProtoField(id = 1)
public String name;
@ProtoField(id = 2)
public Long count;
}
@@ -0,0 +1,134 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.netflix.conductor.annotations.protogen.ProtoEnum;
import com.netflix.conductor.annotations.protogen.ProtoMessage;
import com.netflix.conductor.annotationsprocessor.protogen.types.MessageType;
import com.netflix.conductor.annotationsprocessor.protogen.types.TypeMapper;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
public abstract class AbstractMessage {
protected Class<?> clazz;
protected MessageType type;
protected List<Field> fields = new ArrayList<Field>();
protected List<AbstractMessage> nested = new ArrayList<>();
public AbstractMessage(Class<?> cls, MessageType parentType) {
assert cls.isAnnotationPresent(ProtoMessage.class)
|| cls.isAnnotationPresent(ProtoEnum.class);
this.clazz = cls;
this.type = TypeMapper.INSTANCE.declare(cls, parentType);
for (Class<?> nested : clazz.getDeclaredClasses()) {
if (nested.isEnum()) addNestedEnum(nested);
else addNestedClass(nested);
}
}
private void addNestedEnum(Class<?> cls) {
ProtoEnum ann = (ProtoEnum) cls.getAnnotation(ProtoEnum.class);
if (ann != null) {
nested.add(new Enum(cls, this.type));
}
}
private void addNestedClass(Class<?> cls) {
ProtoMessage ann = (ProtoMessage) cls.getAnnotation(ProtoMessage.class);
if (ann != null) {
nested.add(new Message(cls, this.type));
}
}
public abstract String getProtoClass();
protected abstract void javaMapToProto(TypeSpec.Builder builder);
protected abstract void javaMapFromProto(TypeSpec.Builder builder);
public void generateJavaMapper(TypeSpec.Builder builder) {
javaMapToProto(builder);
javaMapFromProto(builder);
for (AbstractMessage abstractMessage : this.nested) {
abstractMessage.generateJavaMapper(builder);
}
}
public void generateAbstractMethods(Set<MethodSpec> specs) {
for (Field field : fields) {
field.generateAbstractMethods(specs);
}
for (AbstractMessage elem : nested) {
elem.generateAbstractMethods(specs);
}
}
public void findDependencies(Set<String> dependencies) {
for (Field field : fields) {
field.getDependencies(dependencies);
}
for (AbstractMessage elem : nested) {
elem.findDependencies(dependencies);
}
}
public List<AbstractMessage> getNested() {
return nested;
}
public List<Field> getFields() {
return fields;
}
public String getName() {
return clazz.getSimpleName();
}
public abstract static class Field {
protected int protoIndex;
protected java.lang.reflect.Field field;
protected Field(int index, java.lang.reflect.Field field) {
this.protoIndex = index;
this.field = field;
}
public abstract String getProtoTypeDeclaration();
public int getProtoIndex() {
return protoIndex;
}
public String getName() {
return field.getName();
}
public String getProtoName() {
return field.getName().toUpperCase();
}
public void getDependencies(Set<String> deps) {}
public void generateAbstractMethods(Set<MethodSpec> specs) {}
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import javax.lang.model.element.Modifier;
import com.netflix.conductor.annotationsprocessor.protogen.types.MessageType;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
public class Enum extends AbstractMessage {
public enum MapType {
FROM_PROTO("fromProto"),
TO_PROTO("toProto");
private final String methodName;
MapType(String m) {
methodName = m;
}
public String getMethodName() {
return methodName;
}
}
public Enum(Class cls, MessageType parent) {
super(cls, parent);
int protoIndex = 0;
for (java.lang.reflect.Field field : cls.getDeclaredFields()) {
if (field.isEnumConstant()) fields.add(new EnumField(protoIndex++, field));
}
}
@Override
public String getProtoClass() {
return "enum";
}
private MethodSpec javaMap(MapType mt, TypeName from, TypeName to) {
MethodSpec.Builder method = MethodSpec.methodBuilder(mt.getMethodName());
method.addModifiers(Modifier.PUBLIC);
method.returns(to);
method.addParameter(from, "from");
method.addStatement("$T to", to);
method.beginControlFlow("switch (from)");
for (Field field : fields) {
String fromName = (mt == MapType.TO_PROTO) ? field.getName() : field.getProtoName();
String toName = (mt == MapType.TO_PROTO) ? field.getProtoName() : field.getName();
method.addStatement("case $L: to = $T.$L; break", fromName, to, toName);
}
method.addStatement(
"default: throw new $T(\"Unexpected enum constant: \" + from)",
IllegalArgumentException.class);
method.endControlFlow();
method.addStatement("return to");
return method.build();
}
@Override
protected void javaMapFromProto(TypeSpec.Builder type) {
type.addMethod(
javaMap(
MapType.FROM_PROTO,
this.type.getJavaProtoType(),
TypeName.get(this.clazz)));
}
@Override
protected void javaMapToProto(TypeSpec.Builder type) {
type.addMethod(
javaMap(MapType.TO_PROTO, TypeName.get(this.clazz), this.type.getJavaProtoType()));
}
public class EnumField extends Field {
protected EnumField(int index, java.lang.reflect.Field field) {
super(index, field);
}
@Override
public String getProtoTypeDeclaration() {
return String.format("%s = %d", getProtoName(), getProtoIndex());
}
}
}
@@ -0,0 +1,141 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.lang.model.element.Modifier;
import com.netflix.conductor.annotations.protogen.ProtoField;
import com.netflix.conductor.annotations.protogen.ProtoMessage;
import com.netflix.conductor.annotationsprocessor.protogen.types.AbstractType;
import com.netflix.conductor.annotationsprocessor.protogen.types.MessageType;
import com.netflix.conductor.annotationsprocessor.protogen.types.TypeMapper;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
public class Message extends AbstractMessage {
public Message(Class<?> cls, MessageType parent) {
super(cls, parent);
for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
ProtoField ann = field.getAnnotation(ProtoField.class);
if (ann == null) continue;
fields.add(new MessageField(ann.id(), field));
}
}
protected ProtoMessage getAnnotation() {
return (ProtoMessage) this.clazz.getAnnotation(ProtoMessage.class);
}
@Override
public String getProtoClass() {
return "message";
}
@Override
protected void javaMapToProto(TypeSpec.Builder type) {
if (!getAnnotation().toProto() || getAnnotation().wrapper()) return;
ClassName javaProtoType = (ClassName) this.type.getJavaProtoType();
MethodSpec.Builder method = MethodSpec.methodBuilder("toProto");
method.addModifiers(Modifier.PUBLIC);
method.returns(javaProtoType);
method.addParameter(this.clazz, "from");
method.addStatement(
"$T to = $T.newBuilder()", javaProtoType.nestedClass("Builder"), javaProtoType);
for (Field field : this.fields) {
if (field instanceof MessageField) {
AbstractType fieldType = ((MessageField) field).getAbstractType();
fieldType.mapToProto(field.getName(), method);
}
}
method.addStatement("return to.build()");
type.addMethod(method.build());
}
@Override
protected void javaMapFromProto(TypeSpec.Builder type) {
if (!getAnnotation().fromProto() || getAnnotation().wrapper()) return;
MethodSpec.Builder method = MethodSpec.methodBuilder("fromProto");
method.addModifiers(Modifier.PUBLIC);
method.returns(this.clazz);
method.addParameter(this.type.getJavaProtoType(), "from");
method.addStatement("$T to = new $T()", this.clazz, this.clazz);
for (Field field : this.fields) {
if (field instanceof MessageField) {
AbstractType fieldType = ((MessageField) field).getAbstractType();
fieldType.mapFromProto(field.getName(), method);
}
}
method.addStatement("return to");
type.addMethod(method.build());
}
public static class MessageField extends Field {
protected AbstractType type;
protected MessageField(int index, java.lang.reflect.Field field) {
super(index, field);
}
public AbstractType getAbstractType() {
if (type == null) {
type = TypeMapper.INSTANCE.get(field.getGenericType());
}
return type;
}
private static Pattern CAMEL_CASE_RE = Pattern.compile("(?<=[a-z])[A-Z]");
private static String toUnderscoreCase(String input) {
Matcher m = CAMEL_CASE_RE.matcher(input);
StringBuilder sb = new StringBuilder();
while (m.find()) {
m.appendReplacement(sb, "_" + m.group());
}
m.appendTail(sb);
return sb.toString().toLowerCase();
}
@Override
public String getProtoTypeDeclaration() {
return String.format(
"%s %s = %d",
getAbstractType().getProtoType(), toUnderscoreCase(getName()), getProtoIndex());
}
@Override
public void getDependencies(Set<String> deps) {
getAbstractType().getDependencies(deps);
}
@Override
public void generateAbstractMethods(Set<MethodSpec> specs) {
getAbstractType().generateAbstractMethods(specs);
}
}
}
@@ -0,0 +1,78 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import java.util.HashSet;
import java.util.Set;
import com.netflix.conductor.annotationsprocessor.protogen.types.TypeMapper;
import com.squareup.javapoet.ClassName;
public class ProtoFile {
public static String PROTO_SUFFIX = "Pb";
private ClassName baseClass;
private AbstractMessage message;
private String filePath;
private String protoPackageName;
private String javaPackageName;
private String goPackageName;
public ProtoFile(
Class<?> object,
String protoPackageName,
String javaPackageName,
String goPackageName) {
this.protoPackageName = protoPackageName;
this.javaPackageName = javaPackageName;
this.goPackageName = goPackageName;
String className = object.getSimpleName() + PROTO_SUFFIX;
this.filePath = "model/" + object.getSimpleName().toLowerCase() + ".proto";
this.baseClass = ClassName.get(this.javaPackageName, className);
this.message = new Message(object, TypeMapper.INSTANCE.baseClass(baseClass, filePath));
}
public String getJavaClassName() {
return baseClass.simpleName();
}
public String getFilePath() {
return filePath;
}
public String getProtoPackageName() {
return protoPackageName;
}
public String getJavaPackageName() {
return javaPackageName;
}
public String getGoPackageName() {
return goPackageName;
}
public AbstractMessage getMessage() {
return message;
}
public Set<String> getIncludes() {
Set<String> includes = new HashSet<>();
message.findDependencies(includes);
includes.remove(this.getFilePath());
return includes;
}
}
@@ -0,0 +1,133 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import javax.lang.model.element.Modifier;
import com.netflix.conductor.annotations.protogen.ProtoMessage;
import com.github.jknack.handlebars.EscapingStrategy;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Template;
import com.github.jknack.handlebars.io.ClassPathTemplateLoader;
import com.github.jknack.handlebars.io.TemplateLoader;
import com.google.common.reflect.ClassPath;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import jakarta.annotation.Generated;
public class ProtoGen {
private static final String GENERATOR_NAME =
"com.netflix.conductor.annotationsprocessor.protogen";
private String protoPackageName;
private String javaPackageName;
private String goPackageName;
private List<ProtoFile> protoFiles = new ArrayList<>();
public ProtoGen(String protoPackageName, String javaPackageName, String goPackageName) {
this.protoPackageName = protoPackageName;
this.javaPackageName = javaPackageName;
this.goPackageName = goPackageName;
}
public void writeMapper(File root, String mapperPackageName) throws IOException {
TypeSpec.Builder protoMapper =
TypeSpec.classBuilder("AbstractProtoMapper")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addAnnotation(
AnnotationSpec.builder(Generated.class)
.addMember("value", "$S", GENERATOR_NAME)
.build());
Set<MethodSpec> abstractMethods = new HashSet<>();
protoFiles.sort(
new Comparator<ProtoFile>() {
public int compare(ProtoFile p1, ProtoFile p2) {
String n1 = p1.getMessage().getName();
String n2 = p2.getMessage().getName();
return n1.compareTo(n2);
}
});
for (ProtoFile protoFile : protoFiles) {
AbstractMessage elem = protoFile.getMessage();
elem.generateJavaMapper(protoMapper);
elem.generateAbstractMethods(abstractMethods);
}
protoMapper.addMethods(abstractMethods);
JavaFile javaFile =
JavaFile.builder(mapperPackageName, protoMapper.build()).indent(" ").build();
File filename = new File(root, "AbstractProtoMapper.java");
try (Writer writer = new FileWriter(filename.toString())) {
System.out.printf("protogen: writing '%s'...\n", filename);
javaFile.writeTo(writer);
}
}
public void writeProtos(File root) throws IOException {
TemplateLoader loader = new ClassPathTemplateLoader("/templates", ".proto");
Handlebars handlebars =
new Handlebars(loader)
.infiniteLoops(true)
.prettyPrint(true)
.with(EscapingStrategy.NOOP);
Template protoFile = handlebars.compile("file");
for (ProtoFile file : protoFiles) {
File filename = new File(root, file.getFilePath());
try (Writer writer = new FileWriter(filename)) {
System.out.printf("protogen: writing '%s'...\n", filename);
protoFile.apply(file, writer);
}
}
}
public void processPackage(File jarFile, String packageName) throws IOException {
if (!jarFile.isFile()) throw new IOException("missing Jar file " + jarFile);
URL[] urls = new URL[] {jarFile.toURI().toURL()};
ClassLoader loader =
new URLClassLoader(urls, Thread.currentThread().getContextClassLoader());
ClassPath cp = ClassPath.from(loader);
System.out.printf("protogen: processing Jar '%s'\n", jarFile);
for (ClassPath.ClassInfo info : cp.getTopLevelClassesRecursive(packageName)) {
try {
processClass(info.load());
} catch (NoClassDefFoundError ignored) {
}
}
}
public void processClass(Class<?> obj) {
if (obj.isAnnotationPresent(ProtoMessage.class)) {
System.out.printf("protogen: found %s\n", obj.getCanonicalName());
protoFiles.add(new ProtoFile(obj, protoPackageName, javaPackageName, goPackageName));
}
}
}
@@ -0,0 +1,151 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import java.io.File;
import java.io.IOException;
public class ProtoGenTask {
private String protoPackage;
private String javaPackage;
private String goPackage;
private File protosDir;
private File mapperDir;
private String mapperPackage;
private File sourceJar;
private String sourcePackage;
public String getProtoPackage() {
return protoPackage;
}
public void setProtoPackage(String protoPackage) {
this.protoPackage = protoPackage;
}
public String getJavaPackage() {
return javaPackage;
}
public void setJavaPackage(String javaPackage) {
this.javaPackage = javaPackage;
}
public String getGoPackage() {
return goPackage;
}
public void setGoPackage(String goPackage) {
this.goPackage = goPackage;
}
public File getProtosDir() {
return protosDir;
}
public void setProtosDir(File protosDir) {
this.protosDir = protosDir;
}
public File getMapperDir() {
return mapperDir;
}
public void setMapperDir(File mapperDir) {
this.mapperDir = mapperDir;
}
public String getMapperPackage() {
return mapperPackage;
}
public void setMapperPackage(String mapperPackage) {
this.mapperPackage = mapperPackage;
}
public File getSourceJar() {
return sourceJar;
}
public void setSourceJar(File sourceJar) {
this.sourceJar = sourceJar;
}
public String getSourcePackage() {
return sourcePackage;
}
public void setSourcePackage(String sourcePackage) {
this.sourcePackage = sourcePackage;
}
public void generate() {
ProtoGen generator = new ProtoGen(protoPackage, javaPackage, goPackage);
try {
generator.processPackage(sourceJar, sourcePackage);
generator.writeMapper(mapperDir, mapperPackage);
generator.writeProtos(protosDir);
} catch (IOException e) {
System.err.printf("protogen: failed with %s\n", e);
}
}
public static void main(String[] args) {
if (args == null || args.length < 8) {
throw new RuntimeException(
"protogen configuration incomplete, please provide all required (8) inputs");
}
ProtoGenTask task = new ProtoGenTask();
int argsId = 0;
task.setProtoPackage(args[argsId++]);
task.setJavaPackage(args[argsId++]);
task.setGoPackage(args[argsId++]);
task.setProtosDir(new File(args[argsId++]));
task.setMapperDir(new File(args[argsId++]));
task.setMapperPackage(args[argsId++]);
task.setSourceJar(new File(args[argsId++]));
task.setSourcePackage(args[argsId]);
System.out.println("Running protogen with arguments: " + task);
task.generate();
System.out.println("protogen completed.");
}
@Override
public String toString() {
return "ProtoGenTask{"
+ "protoPackage='"
+ protoPackage
+ '\''
+ ", javaPackage='"
+ javaPackage
+ '\''
+ ", goPackage='"
+ goPackage
+ '\''
+ ", protosDir="
+ protosDir
+ ", mapperDir="
+ mapperDir
+ ", mapperPackage='"
+ mapperPackage
+ '\''
+ ", sourceJar="
+ sourceJar
+ ", sourcePackage='"
+ sourcePackage
+ '\''
+ '}';
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.Set;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
public abstract class AbstractType {
Type javaType;
TypeName javaProtoType;
AbstractType(Type javaType, TypeName javaProtoType) {
this.javaType = javaType;
this.javaProtoType = javaProtoType;
}
public Type getJavaType() {
return javaType;
}
public TypeName getJavaProtoType() {
return javaProtoType;
}
public abstract String getProtoType();
public abstract TypeName getRawJavaType();
public abstract void mapToProto(String field, MethodSpec.Builder method);
public abstract void mapFromProto(String field, MethodSpec.Builder method);
public abstract void getDependencies(Set<String> deps);
public abstract void generateAbstractMethods(Set<MethodSpec> specs);
protected String javaMethodName(String m, String field) {
String fieldName = field.substring(0, 1).toUpperCase() + field.substring(1);
return m + fieldName;
}
private static class ProtoCase {
static String convert(String s) {
StringBuilder out = new StringBuilder(s.length());
final int len = s.length();
int i = 0;
int j = -1;
while ((j = findWordBoundary(s, ++j)) != -1) {
out.append(normalizeWord(s.substring(i, j)));
if (j < len && s.charAt(j) == '_') j++;
i = j;
}
if (i == 0) return normalizeWord(s);
if (i < len) out.append(normalizeWord(s.substring(i)));
return out.toString();
}
private static boolean isWordBoundary(char c) {
return (c >= 'A' && c <= 'Z');
}
private static int findWordBoundary(CharSequence sequence, int start) {
int length = sequence.length();
if (start >= length) return -1;
if (isWordBoundary(sequence.charAt(start))) {
int i = start;
while (i < length && isWordBoundary(sequence.charAt(i))) i++;
return i;
} else {
for (int i = start; i < length; i++) {
final char c = sequence.charAt(i);
if (c == '_' || isWordBoundary(c)) return i;
}
return -1;
}
}
private static String normalizeWord(String word) {
if (word.length() < 2) return word.toUpperCase();
return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase();
}
}
protected String protoMethodName(String m, String field) {
return m + ProtoCase.convert(field);
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.Set;
import javax.lang.model.element.Modifier;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
public class ExternMessageType extends MessageType {
private String externProtoType;
public ExternMessageType(
Type javaType, ClassName javaProtoType, String externProtoType, String protoFilePath) {
super(javaType, javaProtoType, protoFilePath);
this.externProtoType = externProtoType;
}
@Override
public String getProtoType() {
return externProtoType;
}
@Override
public void generateAbstractMethods(Set<MethodSpec> specs) {
MethodSpec fromProto =
MethodSpec.methodBuilder("fromProto")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.returns(this.getJavaType())
.addParameter(this.getJavaProtoType(), "in")
.build();
MethodSpec toProto =
MethodSpec.methodBuilder("toProto")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.returns(this.getJavaProtoType())
.addParameter(this.getJavaType(), "in")
.build();
specs.add(fromProto);
specs.add(toProto);
}
}
@@ -0,0 +1,72 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Set;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
abstract class GenericType extends AbstractType {
public GenericType(Type type) {
super(type, null);
}
protected Class getRawType() {
ParameterizedType tt = (ParameterizedType) this.getJavaType();
return (Class) tt.getRawType();
}
protected AbstractType resolveGenericParam(int idx) {
ParameterizedType tt = (ParameterizedType) this.getJavaType();
Type[] types = tt.getActualTypeArguments();
AbstractType abstractType = TypeMapper.INSTANCE.get(types[idx]);
if (abstractType instanceof GenericType) {
return WrappedType.wrap((GenericType) abstractType);
}
return abstractType;
}
public abstract String getWrapperSuffix();
public abstract AbstractType getValueType();
public abstract TypeName resolveJavaProtoType();
@Override
public TypeName getRawJavaType() {
return ClassName.get(getRawType());
}
@Override
public void getDependencies(Set<String> deps) {
getValueType().getDependencies(deps);
}
@Override
public void generateAbstractMethods(Set<MethodSpec> specs) {
getValueType().generateAbstractMethods(specs);
}
@Override
public TypeName getJavaProtoType() {
if (javaProtoType == null) {
javaProtoType = resolveJavaProtoType();
}
return javaProtoType;
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.stream.Collectors;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
public class ListType extends GenericType {
private AbstractType valueType;
public ListType(Type type) {
super(type);
}
@Override
public String getWrapperSuffix() {
return "List";
}
@Override
public AbstractType getValueType() {
if (valueType == null) {
valueType = resolveGenericParam(0);
}
return valueType;
}
@Override
public void mapToProto(String field, MethodSpec.Builder method) {
AbstractType subtype = getValueType();
if (subtype instanceof ScalarType) {
method.addStatement(
"to.$L( from.$L() )",
protoMethodName("addAll", field),
javaMethodName("get", field));
} else {
method.beginControlFlow(
"for ($T elem : from.$L())",
subtype.getJavaType(),
javaMethodName("get", field));
method.addStatement("to.$L( toProto(elem) )", protoMethodName("add", field));
method.endControlFlow();
}
}
@Override
public void mapFromProto(String field, MethodSpec.Builder method) {
AbstractType subtype = getValueType();
Type entryType = subtype.getJavaType();
Class collector = TypeMapper.PROTO_LIST_TYPES.get(getRawType());
if (subtype instanceof ScalarType) {
if (entryType.equals(String.class)) {
method.addStatement(
"to.$L( from.$L().stream().collect($T.toCollection($T::new)) )",
javaMethodName("set", field),
protoMethodName("get", field) + "List",
Collectors.class,
collector);
} else {
method.addStatement(
"to.$L( from.$L() )",
javaMethodName("set", field),
protoMethodName("get", field) + "List");
}
} else {
method.addStatement(
"to.$L( from.$L().stream().map(this::fromProto).collect($T.toCollection($T::new)) )",
javaMethodName("set", field),
protoMethodName("get", field) + "List",
Collectors.class,
collector);
}
}
@Override
public TypeName resolveJavaProtoType() {
return ParameterizedTypeName.get(
(ClassName) getRawJavaType(), getValueType().getJavaProtoType());
}
@Override
public String getProtoType() {
return "repeated " + getValueType().getProtoType();
}
}
@@ -0,0 +1,126 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
public class MapType extends GenericType {
private AbstractType keyType;
private AbstractType valueType;
public MapType(Type type) {
super(type);
}
@Override
public String getWrapperSuffix() {
return "Map";
}
@Override
public AbstractType getValueType() {
if (valueType == null) {
valueType = resolveGenericParam(1);
}
return valueType;
}
public AbstractType getKeyType() {
if (keyType == null) {
keyType = resolveGenericParam(0);
}
return keyType;
}
@Override
public void mapToProto(String field, MethodSpec.Builder method) {
AbstractType valueType = getValueType();
if (valueType instanceof ScalarType) {
method.addStatement(
"to.$L( from.$L() )",
protoMethodName("putAll", field),
javaMethodName("get", field));
} else {
TypeName typeName =
ParameterizedTypeName.get(
Map.Entry.class,
getKeyType().getJavaType(),
getValueType().getJavaType());
method.beginControlFlow(
"for ($T pair : from.$L().entrySet())", typeName, javaMethodName("get", field));
method.addStatement(
"to.$L( pair.getKey(), toProto( pair.getValue() ) )",
protoMethodName("put", field));
method.endControlFlow();
}
}
@Override
public void mapFromProto(String field, MethodSpec.Builder method) {
AbstractType valueType = getValueType();
if (valueType instanceof ScalarType) {
method.addStatement(
"to.$L( from.$L() )",
javaMethodName("set", field),
protoMethodName("get", field) + "Map");
} else {
Type keyType = getKeyType().getJavaType();
Type valueTypeJava = getValueType().getJavaType();
TypeName valueTypePb = getValueType().getJavaProtoType();
ParameterizedTypeName entryType =
ParameterizedTypeName.get(
ClassName.get(Map.Entry.class), TypeName.get(keyType), valueTypePb);
ParameterizedTypeName mapType =
ParameterizedTypeName.get(Map.class, keyType, valueTypeJava);
ParameterizedTypeName hashMapType =
ParameterizedTypeName.get(HashMap.class, keyType, valueTypeJava);
String mapName = field + "Map";
method.addStatement("$T $L = new $T()", mapType, mapName, hashMapType);
method.beginControlFlow(
"for ($T pair : from.$L().entrySet())",
entryType,
protoMethodName("get", field) + "Map");
method.addStatement("$L.put( pair.getKey(), fromProto( pair.getValue() ) )", mapName);
method.endControlFlow();
method.addStatement("to.$L($L)", javaMethodName("set", field), mapName);
}
}
@Override
public TypeName resolveJavaProtoType() {
return ParameterizedTypeName.get(
(ClassName) getRawJavaType(),
getKeyType().getJavaProtoType(),
getValueType().getJavaProtoType());
}
@Override
public String getProtoType() {
AbstractType keyType = getKeyType();
AbstractType valueType = getValueType();
if (!(keyType instanceof ScalarType)) {
throw new IllegalArgumentException(
"cannot map non-scalar map key: " + this.getJavaType());
}
return String.format("map<%s, %s>", keyType.getProtoType(), valueType.getProtoType());
}
}
@@ -0,0 +1,78 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
public class MessageType extends AbstractType {
private String protoFilePath;
public MessageType(Type javaType, ClassName javaProtoType, String protoFilePath) {
super(javaType, javaProtoType);
this.protoFilePath = protoFilePath;
}
@Override
public String getProtoType() {
List<String> classes = ((ClassName) getJavaProtoType()).simpleNames();
return String.join(".", classes.subList(1, classes.size()));
}
public String getProtoFilePath() {
return protoFilePath;
}
@Override
public TypeName getRawJavaType() {
return getJavaProtoType();
}
@Override
public void mapToProto(String field, MethodSpec.Builder method) {
final String getter = javaMethodName("get", field);
method.beginControlFlow("if (from.$L() != null)", getter);
method.addStatement("to.$L( toProto( from.$L() ) )", protoMethodName("set", field), getter);
method.endControlFlow();
}
private boolean isEnum() {
Type clazz = getJavaType();
return (clazz instanceof Class<?>) && ((Class) clazz).isEnum();
}
@Override
public void mapFromProto(String field, MethodSpec.Builder method) {
if (!isEnum()) method.beginControlFlow("if (from.$L())", protoMethodName("has", field));
method.addStatement(
"to.$L( fromProto( from.$L() ) )",
javaMethodName("set", field),
protoMethodName("get", field));
if (!isEnum()) method.endControlFlow();
}
@Override
public void getDependencies(Set<String> deps) {
deps.add(protoFilePath);
}
@Override
public void generateAbstractMethods(Set<MethodSpec> specs) {}
}
@@ -0,0 +1,78 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.Set;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
public class ScalarType extends AbstractType {
private String protoType;
public ScalarType(Type javaType, TypeName javaProtoType, String protoType) {
super(javaType, javaProtoType);
this.protoType = protoType;
}
@Override
public String getProtoType() {
return protoType;
}
@Override
public TypeName getRawJavaType() {
return getJavaProtoType();
}
@Override
public void mapFromProto(String field, MethodSpec.Builder method) {
method.addStatement(
"to.$L( from.$L() )", javaMethodName("set", field), protoMethodName("get", field));
}
private boolean isNullableType() {
final Type jt = getJavaType();
return jt.equals(Boolean.class)
|| jt.equals(Byte.class)
|| jt.equals(Character.class)
|| jt.equals(Short.class)
|| jt.equals(Integer.class)
|| jt.equals(Long.class)
|| jt.equals(Double.class)
|| jt.equals(Float.class)
|| jt.equals(String.class);
}
@Override
public void mapToProto(String field, MethodSpec.Builder method) {
final boolean nullable = isNullableType();
String getter =
(getJavaType().equals(boolean.class) || getJavaType().equals(Boolean.class))
? javaMethodName("is", field)
: javaMethodName("get", field);
if (nullable) method.beginControlFlow("if (from.$L() != null)", getter);
method.addStatement("to.$L( from.$L() )", protoMethodName("set", field), getter);
if (nullable) method.endControlFlow();
}
@Override
public void getDependencies(Set<String> deps) {}
@Override
public void generateAbstractMethods(Set<MethodSpec> specs) {}
}
@@ -0,0 +1,115 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import com.google.protobuf.Any;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
public class TypeMapper {
static Map<Type, Class> PROTO_LIST_TYPES = new HashMap<>();
static {
PROTO_LIST_TYPES.put(List.class, ArrayList.class);
PROTO_LIST_TYPES.put(Set.class, HashSet.class);
PROTO_LIST_TYPES.put(LinkedList.class, LinkedList.class);
}
public static TypeMapper INSTANCE = new TypeMapper();
private Map<Type, AbstractType> types = new HashMap<>();
public void addScalarType(Type t, String protoType) {
types.put(t, new ScalarType(t, TypeName.get(t), protoType));
}
public void addMessageType(Class<?> t, MessageType message) {
types.put(t, message);
}
public TypeMapper() {
addScalarType(int.class, "int32");
addScalarType(Integer.class, "int32");
addScalarType(long.class, "int64");
addScalarType(Long.class, "int64");
addScalarType(String.class, "string");
addScalarType(boolean.class, "bool");
addScalarType(Boolean.class, "bool");
addMessageType(
Object.class,
new ExternMessageType(
Object.class,
ClassName.get("com.google.protobuf", "Value"),
"google.protobuf.Value",
"google/protobuf/struct.proto"));
addMessageType(
Any.class,
new ExternMessageType(
Any.class,
ClassName.get(Any.class),
"google.protobuf.Any",
"google/protobuf/any.proto"));
}
public AbstractType get(Type t) {
if (!types.containsKey(t)) {
if (t instanceof ParameterizedType) {
Type raw = ((ParameterizedType) t).getRawType();
if (PROTO_LIST_TYPES.containsKey(raw)) {
types.put(t, new ListType(t));
} else if (raw.equals(Map.class)) {
types.put(t, new MapType(t));
}
}
}
if (!types.containsKey(t)) {
throw new IllegalArgumentException("Cannot map type: " + t);
}
return types.get(t);
}
public MessageType get(String className) {
for (Map.Entry<Type, AbstractType> pair : types.entrySet()) {
AbstractType t = pair.getValue();
if (t instanceof MessageType) {
if (((Class) t.getJavaType()).getSimpleName().equals(className))
return (MessageType) t;
}
}
return null;
}
public MessageType declare(Class type, MessageType parent) {
return declare(type, (ClassName) parent.getJavaProtoType(), parent.getProtoFilePath());
}
public MessageType declare(Class type, ClassName parentType, String protoFilePath) {
String simpleName = type.getSimpleName();
MessageType t = new MessageType(type, parentType.nestedClass(simpleName), protoFilePath);
if (types.containsKey(type)) {
throw new IllegalArgumentException("duplicate type declaration: " + type);
}
types.put(type, t);
return t;
}
public MessageType baseClass(ClassName className, String protoFilePath) {
return new MessageType(Object.class, className, protoFilePath);
}
}
@@ -0,0 +1,90 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen.types;
import java.lang.reflect.Type;
import java.util.Set;
import javax.lang.model.element.Modifier;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
public class WrappedType extends AbstractType {
private AbstractType realType;
private MessageType wrappedType;
public static WrappedType wrap(GenericType realType) {
Type valueType = realType.getValueType().getJavaType();
if (!(valueType instanceof Class))
throw new IllegalArgumentException("cannot wrap primitive type: " + valueType);
String className = ((Class) valueType).getSimpleName() + realType.getWrapperSuffix();
MessageType wrappedType = TypeMapper.INSTANCE.get(className);
if (wrappedType == null)
throw new IllegalArgumentException("missing wrapper class: " + className);
return new WrappedType(realType, wrappedType);
}
public WrappedType(AbstractType realType, MessageType wrappedType) {
super(realType.getJavaType(), wrappedType.getJavaProtoType());
this.realType = realType;
this.wrappedType = wrappedType;
}
@Override
public String getProtoType() {
return wrappedType.getProtoType();
}
@Override
public TypeName getRawJavaType() {
return realType.getRawJavaType();
}
@Override
public void mapToProto(String field, MethodSpec.Builder method) {
wrappedType.mapToProto(field, method);
}
@Override
public void mapFromProto(String field, MethodSpec.Builder method) {
wrappedType.mapFromProto(field, method);
}
@Override
public void getDependencies(Set<String> deps) {
this.realType.getDependencies(deps);
this.wrappedType.getDependencies(deps);
}
@Override
public void generateAbstractMethods(Set<MethodSpec> specs) {
MethodSpec fromProto =
MethodSpec.methodBuilder("fromProto")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.returns(this.realType.getJavaType())
.addParameter(this.wrappedType.getJavaProtoType(), "in")
.build();
MethodSpec toProto =
MethodSpec.methodBuilder("toProto")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.returns(this.wrappedType.getJavaProtoType())
.addParameter(this.realType.getJavaType(), "in")
.build();
specs.add(fromProto);
specs.add(toProto);
}
}
@@ -0,0 +1,14 @@
syntax = "proto3";
package {{protoPackageName}};
{{#includes}}
import "{{this}}";
{{/includes}}
option java_package = "{{javaPackageName}}";
option java_outer_classname = "{{javaClassName}}";
option go_package = "{{goPackageName}}";
{{#message}}
{{>message}}
{{/message}}
@@ -0,0 +1,8 @@
{{protoClass}} {{name}} {
{{#nested}}
{{>message}}
{{/nested}}
{{#fields}}
{{protoTypeDeclaration}};
{{/fields}}
}
@@ -0,0 +1,70 @@
/*
* Copyright 2022 Conductor Authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.conductor.annotationsprocessor.protogen;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import static org.junit.Assert.*;
public class ProtoGenTest {
private static final Charset charset = StandardCharsets.UTF_8;
@Rule public TemporaryFolder folder = new TemporaryFolder();
@Test
public void happyPath() throws Exception {
File rootDir = folder.getRoot();
String protoPackage = "protoPackage";
String javaPackage = "abc.protogen.example";
String goPackage = "goPackage";
String sourcePackage = "com.example";
String mapperPackage = "mapperPackage";
File jarFile = new File("./build/libs/example.jar");
assertTrue(jarFile.exists());
File mapperDir = new File(rootDir, "mapperDir");
mapperDir.mkdirs();
File protosDir = new File(rootDir, "protosDir");
protosDir.mkdirs();
File modelDir = new File(protosDir, "model");
modelDir.mkdirs();
ProtoGen generator = new ProtoGen(protoPackage, javaPackage, goPackage);
generator.processPackage(jarFile, sourcePackage);
generator.writeMapper(mapperDir, mapperPackage);
generator.writeProtos(protosDir);
List<File> models = Lists.newArrayList(modelDir.listFiles());
assertEquals(1, models.size());
File exampleProtoFile =
models.stream().filter(f -> f.getName().equals("example.proto")).findFirst().get();
assertTrue(exampleProtoFile.length() > 0);
assertEquals(
Resources.asCharSource(Resources.getResource("example.proto.txt"), charset).read(),
Files.asCharSource(exampleProtoFile, charset).read());
}
}
@@ -0,0 +1,12 @@
syntax = "proto3";
package protoPackage;
option java_package = "abc.protogen.example";
option java_outer_classname = "ExamplePb";
option go_package = "goPackage";
message Example {
string name = 1;
int64 count = 2;
}