Skip to content

Commit

Permalink
IPROTO-307 @proto annotation
Browse files Browse the repository at this point in the history
* IPROTO-306 Record support
  • Loading branch information
tristantarrant authored and karesti committed Mar 11, 2024
1 parent a93d333 commit 19c364f
Show file tree
Hide file tree
Showing 11 changed files with 452 additions and 236 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.infinispan.protostream.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Defines a Protocol Buffers message without having to annotate all fields with {@link ProtoField}.
* Use this annotation to quickly generate messages from records or classes with public fields.
* Fields must be public and they will be assigned incremental numbers based on the declaration order.
* It is possible to override the automated defaults for a field by using the {@link ProtoField} annotation.
*
* @since 5.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Proto {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import java.lang.annotation.Target;

/**
* An optional annotation for specifying the a numeric type identifier for a Protobuf message or enum type. This numeric
* identifier must be globally unique so it can be used to identify the type instead of the fully qualified name.
* An optional annotation for specifying a numeric type identifier for a Protobuf message or enum type. This numeric
* identifier must be globally unique, so it can be used to identify the type instead of the fully qualified name.
* <p>
* This Java annotations results in a protostream documentation annotation 'TypeId' being added to the generated proto
* schema.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -545,8 +545,8 @@ private void generateFieldReadMethod(ProtoMessageTypeMetadata messageTypeMetadat
iw.println("int $len = $in.readUInt32();");
iw.println("int $limit = $in.pushLimit($len);");
iw.println("int $t = $in.readTag();");
String key = generateMapFieldReadMethod(mapMetadata.getKey(), iw, noFactory, true);
String value = generateMapFieldReadMethod(mapMetadata.getValue(), iw, noFactory, false);
String key = generateMapFieldReadMethod(mapMetadata.getKey(), iw, true);
String value = generateMapFieldReadMethod(mapMetadata.getValue(), iw, false);
iw.printf("%s.put(%s, %s);\n", makeCollectionLocalVar(mapMetadata), key, value);
iw.println("$in.checkLastTagWas(0);");
iw.println("$in.popLimit($limit);");
Expand All @@ -559,11 +559,9 @@ private void generateFieldReadMethod(ProtoMessageTypeMetadata messageTypeMetadat
iw.dec().println("}");
}

private String generateMapFieldReadMethod(ProtoFieldMetadata fieldMetadata, IndentWriter iw, boolean noFactory, boolean readNext) {
final String v = makeFieldLocalVar(fieldMetadata);
if (noFactory || fieldMetadata.isRepeated()) {
iw.printf("%s %s = %s;\n", fieldMetadata.getJavaTypeName(), v, fieldMetadata.getProtobufType().getJavaType().defaultValueAsString());
}
private String generateMapFieldReadMethod(ProtoFieldMetadata fieldMetadata, IndentWriter iw, boolean readNext) {
final String v = "__mv$" + fieldMetadata.getNumber();
iw.printf("%s %s = %s;\n", fieldMetadata.getJavaTypeName(), v, fieldMetadata.getProtobufType().getJavaType().defaultValueAsString());
iw.printf("if ($t == %s) {\n", makeFieldTag(fieldMetadata.getNumber(), fieldMetadata.getProtobufType().getWireType()));
iw.inc();
if (BaseProtoSchemaGenerator.generateMarshallerDebugComments) {
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,8 @@ default boolean isAbstract() {
default boolean isInterface() {
return Modifier.isInterface(getModifiers());
}

default boolean isRecord() {
return isAssignableTo(Record.class);
}
}
24 changes: 24 additions & 0 deletions core/src/main/java/org/infinispan/protostream/impl/Log.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,30 @@ default MalformedProtobufException messageTruncated() {
@Message(value = "Invalid default value for field '%s' of Java type %s from class %s: the %s enum must have a 0 value", id = 20)
ProtoSchemaBuilderException noDefaultEnum(String fieldName, String canonicalName, String canonicalName1, String fullName);

@Message(value = "@ProtoFactory annotated %s signature mismatch. The first parameter is expected to be of type 'int' : %s", id = 21)
ProtoSchemaBuilderException factorySignatureMismatch(String kind, String factory);

@Message(value = "@ProtoFactory annotated %s signature mismatch. Expected %d parameters but found %d : %s", id = 22)
ProtoSchemaBuilderException factorySignatureMismatch(String kind, int expected, int found, String factory);

@Message(value = "@ProtoFactory annotated %s signature mismatch. The parameter '%s' does not match any field : %s", id = 23)
ProtoSchemaBuilderException factorySignatureMismatch(String kind, String parameterName, String factory);

@Message(value = "@ProtoFactory annotated %s signature mismatch: %s. The parameter '%s' does not match the type from the field definition.", id = 24)
ProtoSchemaBuilderException factorySignatureMismatchType(String kind, String factory, String parameterName);

@Message(value = "Found more than one @ProtoFactory annotated method / constructor : %s", id = 25)
ProtoSchemaBuilderException multipleFactories(String s);

@Message(value = "@ProtoFactory annotated constructor must not be private: %s", id = 26)
ProtoSchemaBuilderException privateFactory(String s);

@Message(value = "@ProtoFactory annotated method must be static: %s", id =27)
ProtoSchemaBuilderException nonStaticFactory(String s);

@Message(value = "@ProtoFactory annotated method has wrong return type: %s", id = 28)
ProtoSchemaBuilderException wrongFactoryReturnType(String s);

class LogFactory {
public static Log getLog(Class<?> clazz) {
return Logger.getMessageLogger(Log.class, clazz.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import javax.tools.Diagnostic;

import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.Proto;
import org.infinispan.protostream.annotations.ProtoAdapter;
import org.infinispan.protostream.annotations.ProtoEnumValue;
import org.infinispan.protostream.annotations.ProtoFactory;
Expand Down Expand Up @@ -153,6 +154,10 @@ void discoverClasses(RoundEnvironment roundEnv) throws AnnotationProcessingExcep
visitProtoName(e);
}

for (Element e : roundEnv.getElementsAnnotatedWith(Proto.class)) {
visitProtoMessage(e);
}

for (Element e : roundEnv.getElementsAnnotatedWith(ProtoAdapter.class)) {
visitProtoAdapter(e);
}
Expand Down Expand Up @@ -181,6 +186,9 @@ private void visitTypeElement(TypeElement e) {
if (e.getAnnotation(ProtoName.class) != null) {
visitProtoName(e);
}
if (e.getAnnotation(Proto.class) != null) {
visitProtoMessage(e);
}

for (Element member : e.getEnclosedElements()) {
if (member.getAnnotation(ProtoField.class) != null) {
Expand Down Expand Up @@ -244,7 +252,7 @@ private void visitProtoName(Element e) {
}

private void visitProtoMessage(Element e) {
if (e.getKind() != ElementKind.CLASS && e.getKind() != ElementKind.INTERFACE) {
if (e.getKind() != ElementKind.CLASS && e.getKind() != ElementKind.INTERFACE && e.getKind() != ElementKind.RECORD) {
throw new AnnotationProcessingException(e, "@ProtoMessage can only be applied to classes and interfaces.");
}
collectClasses((TypeElement) e);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.infinispan.protostream.annotations.impl.processor;

import java.util.Collection;
import java.util.Map;

import javax.lang.model.type.TypeMirror;

import org.infinispan.protostream.annotations.ProtoField;
Expand All @@ -18,19 +21,31 @@ class CompileTimeProtoMessageTypeMetadata extends ProtoMessageTypeMetadata {

@Override
protected XClass getCollectionImplementationFromAnnotation(ProtoField annotation) {
TypeMirror typeMirror = DangerousActions.getTypeMirror(annotation, ProtoField::collectionImplementation);
return ((MirrorTypeFactory) typeFactory).fromTypeMirror(typeMirror);
if (annotation == null) {
return typeFactory.fromClass(Collection.class);
} else {
TypeMirror typeMirror = DangerousActions.getTypeMirror(annotation, ProtoField::collectionImplementation);
return ((MirrorTypeFactory) typeFactory).fromTypeMirror(typeMirror);
}
}

@Override
protected XClass getMapImplementationFromAnnotation(ProtoField annotation) {
TypeMirror typeMirror = DangerousActions.getTypeMirror(annotation, ProtoField::mapImplementation);
return ((MirrorTypeFactory) typeFactory).fromTypeMirror(typeMirror);
if (annotation == null) {
return typeFactory.fromClass(Map.class);
} else {
TypeMirror typeMirror = DangerousActions.getTypeMirror(annotation, ProtoField::mapImplementation);
return ((MirrorTypeFactory) typeFactory).fromTypeMirror(typeMirror);
}
}

@Override
protected XClass getJavaTypeFromAnnotation(ProtoField annotation) {
TypeMirror typeMirror = DangerousActions.getTypeMirror(annotation, ProtoField::javaType);
return ((MirrorTypeFactory) typeFactory).fromTypeMirror(typeMirror);
if (annotation == null) {
return typeFactory.fromClass(void.class);
} else {
TypeMirror typeMirror = DangerousActions.getTypeMirror(annotation, ProtoField::javaType);
return ((MirrorTypeFactory) typeFactory).fromTypeMirror(typeMirror);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.infinispan.protostream.SerializationContextInitializer;
import org.infinispan.protostream.WrappedMessage;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
import org.infinispan.protostream.annotations.Proto;
import org.infinispan.protostream.annotations.ProtoComment;
import org.infinispan.protostream.annotations.ProtoFactory;
import org.infinispan.protostream.annotations.ProtoField;
Expand All @@ -33,6 +34,12 @@
import org.infinispan.protostream.annotations.ProtoSyntax;
import org.infinispan.protostream.annotations.impl.processor.tests.testdomain.SimpleClass;
import org.infinispan.protostream.annotations.impl.processor.tests.testdomain.SimpleEnum;
import org.infinispan.protostream.annotations.impl.processor.tests.testdomain.SimpleRecord;
import org.infinispan.protostream.descriptors.Descriptor;
import org.infinispan.protostream.descriptors.FieldDescriptor;
import org.infinispan.protostream.descriptors.Label;
import org.infinispan.protostream.descriptors.MapDescriptor;
import org.infinispan.protostream.descriptors.Type;
import org.junit.Test;

public class ProtoSchemaTest {
Expand Down Expand Up @@ -136,6 +143,7 @@ public static class EmbeddedLifespanExpirableMetadata extends EmbeddedMetadata {
// EmbeddedMetadata.class,
EmbeddedMetadata.EmbeddedLifespanExpirableMetadata.class,
SimpleEnum.class,
SimpleRecord.class,
// String.class,
X.class
}
Expand Down Expand Up @@ -1376,6 +1384,54 @@ public void testGenericMessage() throws Exception {
assertEquals("asdfg", ((GenericMessage.OtherMessage) genericMessage.field4.getValue()).field1);
}

@Proto
static final class BareMessage {
public int anInt;
public String aString;
public List<String> things;
public Map<String, String> moreThings;
}

@ProtoSchema(schemaFileName = "bare_message.proto",
includeClasses = {
BareMessage.class,
}, syntax = ProtoSyntax.PROTO3
)
interface TestBareMessageSerializationContextInitializer extends GeneratedSchema {
}

@Test
public void testBareMessage() {
SerializationContext ctx = ProtobufUtil.newSerializationContext();

GeneratedSchema generatedSchema = new TestBareMessageSerializationContextInitializerImpl();
generatedSchema.registerSchema(ctx);
generatedSchema.registerMarshallers(ctx);

assertTrue(generatedSchema.getProtoFile().contains("message BareMessage"));
Descriptor message = (Descriptor) ctx.getDescriptorByName("BareMessage");
FieldDescriptor field = message.getFields().get(0);
assertEquals("anInt", field.getName());
assertEquals(1, field.getNumber());
assertEquals(Type.INT32, field.getType());
assertEquals(Label.OPTIONAL, field.getLabel());
field = message.getFields().get(1);
assertEquals("aString", field.getName());
assertEquals(2, field.getNumber());
assertEquals(Type.STRING, field.getType());
assertEquals(Label.OPTIONAL, field.getLabel());
field = message.getFields().get(2);
assertEquals("things", field.getName());
assertEquals(3, field.getNumber());
assertEquals(Type.STRING, field.getType());
assertEquals(Label.REPEATED, field.getLabel());
MapDescriptor map = (MapDescriptor) message.getFields().get(3);
assertEquals("moreThings", map.getName());
assertEquals(4, map.getNumber());
assertEquals(Type.STRING, map.getKeyType());
assertEquals(Type.STRING, map.getType());
}

//todo warnings logged to log4j during generation do not end up in compiler's message log

//todo provide a sensible value() alias for all @ProtoXyz annotations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public class SimpleClass {
@ProtoField(number = 314, name = "my_enum_field", defaultValue = "AX")
public SimpleEnum myEnumField;

@ProtoField(number = 400, name ="my_record")
public SimpleRecord rec;

//TODO here we have several cases not covered by tests...
/*
private Integer x;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.infinispan.protostream.annotations.impl.processor.tests.testdomain;

import java.util.List;
import java.util.Map;

import org.infinispan.protostream.annotations.Proto;

@Proto
public record SimpleRecord(String aString, int anInt, SimpleEnum anEnum, List<String> things, float[] someFloats, Map<String, String> aMapOfStrings) {
}

0 comments on commit 19c364f

Please sign in to comment.