diff --git a/core/src/main/java/org/infinispan/protostream/annotations/Proto.java b/core/src/main/java/org/infinispan/protostream/annotations/Proto.java index f7c2daee0..47613a099 100644 --- a/core/src/main/java/org/infinispan/protostream/annotations/Proto.java +++ b/core/src/main/java/org/infinispan/protostream/annotations/Proto.java @@ -7,10 +7,17 @@ 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. + * Defines a Protocol Buffers message or enum without having to annotate all fields with {@link ProtoField} or {@link ProtoEnumValue}. + *

+ * Use this annotation on records or classes with public fields to quickly generate protocol buffers messages. + * 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. + *

+ *

+ * Use this annotation on Java enums to quickly generate protocol buffer enums. + * The enums will use the natural ordinal number of the values. + * It is possible to override the automated defaults for an enum value by using the {@link ProtoEnumValue} annotation. + *

* * @since 5.0 */ diff --git a/core/src/main/java/org/infinispan/protostream/annotations/impl/ProtoEnumTypeMetadata.java b/core/src/main/java/org/infinispan/protostream/annotations/impl/ProtoEnumTypeMetadata.java index 2c21b55e0..b23ff4ed0 100644 --- a/core/src/main/java/org/infinispan/protostream/annotations/impl/ProtoEnumTypeMetadata.java +++ b/core/src/main/java/org/infinispan/protostream/annotations/impl/ProtoEnumTypeMetadata.java @@ -5,6 +5,7 @@ import java.util.SortedMap; import java.util.TreeMap; +import org.infinispan.protostream.annotations.Proto; import org.infinispan.protostream.annotations.ProtoEnumValue; import org.infinispan.protostream.annotations.ProtoName; import org.infinispan.protostream.annotations.ProtoSchemaBuilderException; @@ -73,20 +74,21 @@ public boolean isAdapter() { @Override public void scanMemberAnnotations() { + final boolean implicit = annotatedEnumClass.getAnnotation(Proto.class) != null; if (membersByNumber == null) { membersByNumber = new TreeMap<>(); membersByName = new HashMap<>(); for (XEnumConstant ec : annotatedEnumClass.getEnumConstants()) { ProtoEnumValue annotation = ec.getAnnotation(ProtoEnumValue.class); - if (annotation == null) { - throw new ProtoSchemaBuilderException("Enum constants must have the @ProtoEnumValue annotation: " + getAnnotatedClassName() + '.' + ec.getName()); + if (annotation == null && !implicit) { + throw Log.LOG.explicitEnumValueAnnotations(ec.getName(), annotatedEnumClass.getName()); } - int number = getNumber(annotation, ec); + int number = annotation == null ? ec.getOrdinal() : getNumber(annotation, ec); if (membersByNumber.containsKey(number)) { throw new ProtoSchemaBuilderException("Found duplicate definition of Protobuf enum tag " + number + " on enum constant: " + getAnnotatedClassName() + '.' + ec.getName() + " clashes with " + membersByNumber.get(number).getJavaEnumName()); } - String name = annotation.name(); + String name = annotation == null ? ec.getName() : annotation.name(); if (name.isEmpty()) { name = ec.getName(); } diff --git a/core/src/main/java/org/infinispan/protostream/impl/Log.java b/core/src/main/java/org/infinispan/protostream/impl/Log.java index 7b224b3f4..65972bdf9 100644 --- a/core/src/main/java/org/infinispan/protostream/impl/Log.java +++ b/core/src/main/java/org/infinispan/protostream/impl/Log.java @@ -114,6 +114,9 @@ default MalformedProtobufException messageTruncated() { @Message(value = "@ProtoFactory annotated method has wrong return type: %s", id = 28) ProtoSchemaBuilderException wrongFactoryReturnType(String s); + @Message(value = "Value `%s` on enum `%s` must be annotated with @ProtoEnumValue", id = 29) + ProtoSchemaBuilderException explicitEnumValueAnnotations(String value, String name); + class LogFactory { public static Log getLog(Class clazz) { return Logger.getMessageLogger(Log.class, clazz.getName()); diff --git a/processor/src/main/java/org/infinispan/protostream/annotations/impl/processor/AnnotatedClassScanner.java b/processor/src/main/java/org/infinispan/protostream/annotations/impl/processor/AnnotatedClassScanner.java index c0e8cee14..4951de815 100644 --- a/processor/src/main/java/org/infinispan/protostream/annotations/impl/processor/AnnotatedClassScanner.java +++ b/processor/src/main/java/org/infinispan/protostream/annotations/impl/processor/AnnotatedClassScanner.java @@ -155,7 +155,7 @@ void discoverClasses(RoundEnvironment roundEnv) throws AnnotationProcessingExcep } for (Element e : roundEnv.getElementsAnnotatedWith(Proto.class)) { - visitProtoMessage(e); + visitProto(e); } for (Element e : roundEnv.getElementsAnnotatedWith(ProtoAdapter.class)) { @@ -187,7 +187,7 @@ private void visitTypeElement(TypeElement e) { visitProtoName(e); } if (e.getAnnotation(Proto.class) != null) { - visitProtoMessage(e); + visitProto(e); } for (Element member : e.getEnclosedElements()) { @@ -238,29 +238,27 @@ private void visitProtoAdapter(Element e) { } private void visitProtoTypeId(Element e) { - if (e.getKind() != ElementKind.CLASS && e.getKind() != ElementKind.INTERFACE && e.getKind() != ElementKind.ENUM) { - throw new AnnotationProcessingException(e, "@ProtoTypeId can only be applied to classes, interfaces and enums."); + switch (e.getKind()) { + case CLASS, INTERFACE, RECORD, ENUM -> collectClasses((TypeElement) e); + default -> + throw new AnnotationProcessingException(e, "@ProtoTypeId can only be applied to classes, records, interfaces and enums."); } - collectClasses((TypeElement) e); } private void visitProtoName(Element e) { - if (e.getKind() != ElementKind.CLASS && e.getKind() != ElementKind.INTERFACE && e.getKind() != ElementKind.ENUM) { - throw new AnnotationProcessingException(e, "@ProtoName can only be applied to classes, interfaces and enums."); - } - collectClasses((TypeElement) e); - } - - private void visitProtoMessage(Element e) { - 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."); + switch (e.getKind()) { + case CLASS, INTERFACE, RECORD, ENUM -> collectClasses((TypeElement) e); + default -> + throw new AnnotationProcessingException(e, "@ProtoName can only be applied to classes, interfaces, records and enums."); } collectClasses((TypeElement) e); } - private void visitProtoEnum(Element e) { - if (e.getKind() != ElementKind.CLASS && e.getKind() != ElementKind.INTERFACE) { - throw new AnnotationProcessingException(e, "@ProtoEnum can only be applied to enums."); + private void visitProto(Element e) { + switch (e.getKind()) { + case CLASS, INTERFACE, RECORD, ENUM -> collectClasses((TypeElement) e); + default -> + throw new AnnotationProcessingException(e, "@Proto can only be applied to classes, interfaces, records and enums."); } collectClasses((TypeElement) e); } diff --git a/processor/src/main/java/org/infinispan/protostream/annotations/impl/processor/CompileTimeProtoSchemaGenerator.java b/processor/src/main/java/org/infinispan/protostream/annotations/impl/processor/CompileTimeProtoSchemaGenerator.java index e68e17e9d..90683b880 100644 --- a/processor/src/main/java/org/infinispan/protostream/annotations/impl/processor/CompileTimeProtoSchemaGenerator.java +++ b/processor/src/main/java/org/infinispan/protostream/annotations/impl/processor/CompileTimeProtoSchemaGenerator.java @@ -15,7 +15,6 @@ import org.infinispan.protostream.annotations.impl.AbstractMarshallerCodeGenerator; import org.infinispan.protostream.annotations.impl.BaseProtoSchemaGenerator; import org.infinispan.protostream.annotations.impl.ImportedProtoTypeMetadata; -import org.infinispan.protostream.annotations.impl.ProtoEnumTypeMetadata; import org.infinispan.protostream.annotations.impl.ProtoTypeMetadata; import org.infinispan.protostream.annotations.impl.processor.dependency.CompileTimeDependency; import org.infinispan.protostream.annotations.impl.processor.types.MirrorTypeFactory; @@ -52,11 +51,6 @@ protected AbstractMarshallerCodeGenerator makeMarshallerCodeGenerator() { return marshallerSourceCodeGenerator; } - @Override - protected ProtoTypeMetadata makeEnumTypeMetadata(XClass javaType) { - return new ProtoEnumTypeMetadata(javaType, getTargetClass(javaType)); - } - @Override protected ProtoTypeMetadata makeMessageTypeMetadata(XClass javaType) { XClass targetClass = getTargetClass(javaType); diff --git a/processor/src/test/java/org/infinispan/protostream/annotations/impl/processor/tests/ProtoSchemaTest.java b/processor/src/test/java/org/infinispan/protostream/annotations/impl/processor/tests/ProtoSchemaTest.java index 82a7908cb..a7f684136 100644 --- a/processor/src/test/java/org/infinispan/protostream/annotations/impl/processor/tests/ProtoSchemaTest.java +++ b/processor/src/test/java/org/infinispan/protostream/annotations/impl/processor/tests/ProtoSchemaTest.java @@ -1384,16 +1384,25 @@ public void testGenericMessage() throws Exception { assertEquals("asdfg", ((GenericMessage.OtherMessage) genericMessage.field4.getValue()).field1); } + @Proto + enum BareEnum { + ZERO, + ONE, + TWO + } + @Proto static final class BareMessage { public int anInt; public String aString; public List things; public Map moreThings; + public BareEnum anEnum; } @ProtoSchema(schemaFileName = "bare_message.proto", includeClasses = { + BareEnum.class, BareMessage.class, }, syntax = ProtoSyntax.PROTO3 ) @@ -1430,6 +1439,10 @@ public void testBareMessage() { assertEquals(4, map.getNumber()); assertEquals(Type.STRING, map.getKeyType()); assertEquals(Type.STRING, map.getType()); + field = message.getFields().get(4); + assertEquals("anEnum", field.getName()); + assertEquals(5, field.getNumber()); + assertEquals(Type.ENUM, field.getType()); } //todo warnings logged to log4j during generation do not end up in compiler's message log