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
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:
@@ -0,0 +1 @@
|
||||
Annotation processor is used to generate protobuf files from the annotations.
|
||||
@@ -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;
|
||||
}
|
||||
+134
@@ -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) {}
|
||||
}
|
||||
}
|
||||
+101
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
+141
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+78
@@ -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;
|
||||
}
|
||||
}
|
||||
+133
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+151
@@ -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
|
||||
+ '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
+101
@@ -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);
|
||||
}
|
||||
}
|
||||
+56
@@ -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);
|
||||
}
|
||||
}
|
||||
+72
@@ -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;
|
||||
}
|
||||
}
|
||||
+101
@@ -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();
|
||||
}
|
||||
}
|
||||
+126
@@ -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());
|
||||
}
|
||||
}
|
||||
+78
@@ -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) {}
|
||||
}
|
||||
+78
@@ -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) {}
|
||||
}
|
||||
+115
@@ -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);
|
||||
}
|
||||
}
|
||||
+90
@@ -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}}
|
||||
}
|
||||
+70
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user