diff --git a/core/src/main/java/org/infinispan/protostream/annotations/impl/ProtoMessageTypeMetadata.java b/core/src/main/java/org/infinispan/protostream/annotations/impl/ProtoMessageTypeMetadata.java index 0b389b82b..54fa8997d 100644 --- a/core/src/main/java/org/infinispan/protostream/annotations/impl/ProtoMessageTypeMetadata.java +++ b/core/src/main/java/org/infinispan/protostream/annotations/impl/ProtoMessageTypeMetadata.java @@ -378,7 +378,7 @@ private void discoverFields(XClass clazz, Set examinedClasses, Map examinedClasses, Map fieldsByNumber, Map fieldsByName, Set oneofs) { + XClass sourceType = access.sourceType(); + Type protobufType = defaultType(annotation, sourceType); + boolean isArray = isArray(sourceType, protobufType); + boolean isIterable = sourceType.isAssignableTo(Iterable.class); + boolean isRepeated = isRepeated(sourceType, protobufType); + boolean isRequired = annotation != null && annotation.required(); + boolean isStream = sourceType.isAssignableTo(Stream.class); + if (isRequired && protoSchemaGenerator.syntax() != ProtoSyntax.PROTO2) { + throw new ProtoSchemaBuilderException("Field '" + fieldName + "' of " + clazz.getCanonicalName() + " cannot be marked required when using \"" + protoSchemaGenerator.syntax() + "\" syntax"); + } + boolean isMap = isMap(sourceType); + if (!clazz.isRecord() && isMap && protoSchemaGenerator.syntax() == ProtoSyntax.PROTO2) { + throw new ProtoSchemaBuilderException("Field '" + fieldName + "' of " + clazz.getCanonicalName() + " of type map is not supported when using \"" + protoSchemaGenerator.syntax() + "\" syntax"); + } + if (isRepeated && isRequired) { + throw new ProtoSchemaBuilderException("Repeated field '" + fieldName + "' of " + clazz.getCanonicalName() + " cannot be marked required."); + } + XClass javaType = getJavaTypeFromAnnotation(annotation); + if (javaType == typeFactory.fromClass(void.class)) { + javaType = isRepeated ? access.typeInfoSource().determineRepeatedElementType() : sourceType; + } + if (javaType == typeFactory.fromClass(byte[].class) && protobufType == Type.MESSAGE) { + protobufType = Type.BYTES; + } + if (!javaType.isArray() && !javaType.isPrimitive() && javaType.isAbstract() && !javaType.isEnum()) { + throw Log.LOG.abstractType(javaType.getCanonicalName(), fieldName, clazz.getCanonicalName()); + } + + protobufType = getProtobufType(javaType, protobufType); + + ProtoTypeMetadata protoTypeMetadata = null; + if (protobufType.getJavaType() == JavaType.ENUM || protobufType.getJavaType() == JavaType.MESSAGE) { + protoTypeMetadata = protoSchemaGenerator.scanAnnotations(javaType); + } + + ProtoFieldMetadata fieldMetadata; + if (isMap) { + XClass repeatedImplementation = getMapImplementation(clazz, sourceType, getMapImplementationFromAnnotation(annotation), fieldName, isRepeated); + XClass keyJavaType = access.typeInfoSource().getTypeArgument(0); + Type keyProtobufType = getProtobufType(keyJavaType, Type.MESSAGE); + if (!keyProtobufType.isValidMapKey()) { + throw new ProtoSchemaBuilderException("The key of the map field '" + fieldName + "' of " + clazz.getName() + " must be either a String or an integral type"); + } + fieldMetadata = access.createMapMetadata(number, fieldName, keyJavaType, javaType, repeatedImplementation, keyProtobufType, protobufType, protoTypeMetadata); + } else { + XClass repeatedImplementation; + if (isArray) { + repeatedImplementation = typeFactory.fromClass(ArrayList.class); + } else { + repeatedImplementation = getCollectionImplementation(clazz, sourceType, getCollectionImplementationFromAnnotation(annotation), fieldName, isRepeated); + } + String oneof = validateOneOf(clazz, fieldsByName, oneofs, annotation, fieldName, isRepeated, isRequired); + Object defaultValue = getDefaultValue(clazz, fieldName, javaType, protobufType, annotation == null ? "" : annotation.defaultValue(), isRepeated); + if (!clazz.isRecord() && !isRequired && !isRepeated && javaType.isPrimitive() && defaultValue == null) { + throw new ProtoSchemaBuilderException("Primitive field '" + fieldName + "' of " + clazz.getCanonicalName() + " is not nullable so it should be either marked required or should have a default value"); + } + fieldMetadata = access.createFieldMetadata(number, fieldName, oneof, javaType, repeatedImplementation, + protobufType, protoTypeMetadata, isRequired, isRepeated, isArray, isIterable, isStream, defaultValue); + } + + ProtoFieldMetadata existing = fieldsByNumber.get(number); + if (isDuplicateField(existing, fieldMetadata)) { + throw new ProtoSchemaBuilderException("Duplicate field definition. Found two field definitions with number " + number + ": in " + + fieldMetadata.getLocation() + " and in " + existing.getLocation()); + } + existing = fieldsByName.get(fieldMetadata.getName()); + if (isDuplicateField(existing, fieldMetadata)) { + throw new ProtoSchemaBuilderException("Duplicate field definition. Found two field definitions with name '" + fieldMetadata.getName() + "': in " + + fieldMetadata.getLocation() + " and in " + existing.getLocation()); + } + + checkReserved(fieldMetadata); + fieldsByNumber.put(number, fieldMetadata); + fieldsByName.put(fieldName, fieldMetadata); + } + private void discoverFieldsFromClassMethods(XClass clazz, Map fieldsByNumber, Map fieldsByName, Set oneofs) { Set skipMethods = new HashSet<>(); @@ -437,11 +557,8 @@ private void discoverFieldsFromClassMethods(XClass clazz, Map fieldsByNumber, Map fieldsByName) { + private void discoverFieldsFromRecord(XClass clazz, Map fieldsByNumber, Map fieldsByName, Set oneofs) { String[] parameterNames = factory.getParameterNames(); XClass[] parameterTypes = factory.getParameterTypes(); Iterator components = clazz.getRecordComponents().iterator(); for (int i = 0; i < factory.getParameterCount(); i++) { - int fieldNumber = i + 1; ProtoField annotation = components.next().getAnnotation(ProtoField.class); + int number = i + 1; String fieldName = parameterNames[i]; - XClass javaType = parameterTypes[i]; - Type protobufType = defaultType(annotation, javaType); - - XMethod getter = clazz.getMethod(fieldName); - boolean isArray = isArray(javaType, protobufType); - boolean isIterable = javaType.isAssignableTo(Iterable.class); - boolean isStream = javaType.isAssignableTo(Stream.class); - boolean isRepeated = isRepeated(javaType, protobufType); - boolean isMap = isMap(javaType); - - // Determine the collection/map implementation - XClass repeatedImplementation = null; - if (isMap) { - repeatedImplementation = getMapImplementation(clazz, javaType, getMapImplementationFromAnnotation(annotation), fieldName, true); - } else if (isArray) { - repeatedImplementation = typeFactory.fromClass(ArrayList.class); - } else if (isRepeated) { - repeatedImplementation = getCollectionImplementation(clazz, javaType, getCollectionImplementationFromAnnotation(annotation), fieldName, true); - } - - if (isRepeated) { - javaType = getter.determineRepeatedElementType(); - } - - if (javaType == typeFactory.fromClass(byte[].class) && protobufType == Type.MESSAGE) { - // MESSAGE is the default and stands for 'undefined', we can override it with a better default - protobufType = Type.BYTES; - } - if (!javaType.isArray() && !javaType.isPrimitive() && javaType.isAbstract() && !javaType.isEnum()) { - throw new ProtoSchemaBuilderException("The type " + javaType.getCanonicalName() + " of field '" + fieldName + "' of " + clazz.getCanonicalName() + " should not be abstract."); - } - - protobufType = getProtobufType(javaType, protobufType); - ProtoTypeMetadata protoTypeMetadata = null; - if (protobufType.getJavaType() == JavaType.ENUM || protobufType.getJavaType() == JavaType.MESSAGE) { - protoTypeMetadata = protoSchemaGenerator.scanAnnotations(javaType); - } - String oneof = null; - Object defaultValue; - if (annotation == null) { - defaultValue = getDefaultValue(clazz, fieldName, javaType, protobufType, "", false); - } else { - if (annotation.number() > 0) fieldNumber = annotation.number(); + if (annotation != null) { + if (annotation.number() > 0) number = annotation.number(); if (!annotation.name().isEmpty()) fieldName = annotation.name(); - if (!annotation.oneof().isEmpty()) oneof = annotation.oneof(); - defaultValue = getDefaultValue(clazz, fieldName, javaType, protobufType, annotation.defaultValue(), false); - } - ProtoFieldMetadata fieldMetadata; - if (isMap) { - XClass keyJavaType = getter.getTypeArgument(0); - Type keyType = getProtobufType(keyJavaType, Type.MESSAGE); - if (!keyType.isValidMapKey()) { - throw new ProtoSchemaBuilderException("The key of the map field '" + fieldName + "' of " + clazz.getName() + " must be either a String or an integral type, while processing " + this.protoSchemaGenerator.generator); - } - fieldMetadata = new ProtoMapMetadata(fieldNumber, fieldName, keyJavaType, javaType, repeatedImplementation, keyType, protobufType, protoTypeMetadata, fieldName, getter, getter, null); - } else { - fieldMetadata = new ProtoFieldMetadata(fieldNumber, fieldName, oneof, javaType, - repeatedImplementation, protobufType, protoTypeMetadata, - false, isRepeated, isArray, isIterable, isStream, defaultValue, fieldName, - getter, getter, null); - } - checkReserved(fieldMetadata); - fieldsByNumber.put(fieldMetadata.getNumber(), fieldMetadata); - fieldsByName.put(fieldMetadata.getName(), fieldMetadata); + } + XMethod getter = clazz.getMethod(parameterNames[i]); + FieldAccess access = FieldAccess.ofMethod(parameterNames[i], parameterTypes[i], getter, getter, null); + processProtoField(clazz, number, fieldName, annotation, access, fieldsByNumber, fieldsByName, oneofs); } }