diff --git a/config/config-annotation-processor/src/main/java/ru/tinkoff/kora/config/annotation/processor/ConfigUtils.java b/config/config-annotation-processor/src/main/java/ru/tinkoff/kora/config/annotation/processor/ConfigUtils.java index 48bc3b536..4ba815b24 100644 --- a/config/config-annotation-processor/src/main/java/ru/tinkoff/kora/config/annotation/processor/ConfigUtils.java +++ b/config/config-annotation-processor/src/main/java/ru/tinkoff/kora/config/annotation/processor/ConfigUtils.java @@ -151,12 +151,14 @@ class FieldAndAccessors { equals = method; } else if (name.equals("hashCode") && method.getParameters().isEmpty()) { hashCode = method; - } else { + } else if (method.getParameters().isEmpty()) { if (name.startsWith("get")) { fieldsWithAccessors.computeIfAbsent(CommonUtils.decapitalize(name.substring(3)), n -> new FieldAndAccessors()).getter = method; - } else if (name.startsWith("set")) { - fieldsWithAccessors.computeIfAbsent(CommonUtils.decapitalize(name.substring(3)), n -> new FieldAndAccessors()).setter = method; + } else { + fieldsWithAccessors.computeIfAbsent(name, n -> new FieldAndAccessors()).getter = method; } + } else if (method.getParameters().size() == 1 && name.startsWith("set")) { + fieldsWithAccessors.computeIfAbsent(CommonUtils.decapitalize(name.substring(3)), n -> new FieldAndAccessors()).setter = method; } } } @@ -167,24 +169,27 @@ class FieldAndAccessors { var constructors = CommonUtils.findConstructors(te, m -> m.contains(Modifier.PUBLIC)); var emptyConstructor = constructors.stream().filter(e -> e.getParameters().isEmpty()).findFirst().orElse(null); var nonEmptyConstructor = constructors.stream().filter(e -> !e.getParameters().isEmpty()).findFirst().orElse(null); - var constructorParams = nonEmptyConstructor == null ? Set.of() : nonEmptyConstructor.getParameters().stream().map(VariableElement::getSimpleName).map(Objects::toString).collect(Collectors.toSet()); + var constructorParams = nonEmptyConstructor == null ? Map.of() : nonEmptyConstructor.getParameters().stream().collect(Collectors.toMap( + p -> p.getSimpleName().toString(), + p -> p + )); var seen = new HashSet(); var fields = new ArrayList(); for (var fieldWithAccessors : fieldsWithAccessors.entrySet()) { var name = fieldWithAccessors.getKey(); var value = fieldWithAccessors.getValue(); - if (value.getter == null) { + if (value.getter == null || value.field == null) { continue; } - if (value.setter == null && !constructorParams.contains(value.field.getSimpleName().toString())) { + if (value.setter == null && !constructorParams.containsKey(value.field.getSimpleName().toString())) { continue; } var fieldType = types.asMemberOf(typeMirror, value.field); if (seen.add(name)) { var isNullable = CommonUtils.isNullable(value.field) && !fieldType.getKind().isPrimitive(); var mapping = CommonUtils.parseMapping(value.field).getMapping(ConfigClassNames.configValueExtractor); - var hasDefault = emptyConstructor != null || !constructorParams.contains(value.field.getSimpleName().toString()); + var hasDefault = emptyConstructor != null || !constructorParams.containsKey(value.field.getSimpleName().toString()); fields.add(new ConfigUtils.ConfigField( name, TypeName.get(fieldType), isNullable, hasDefault, mapping )); diff --git a/config/config-annotation-processor/src/test/java/ru/tinkoff/kora/config/annotation/processor/AnnotationConfigTest.java b/config/config-annotation-processor/src/test/java/ru/tinkoff/kora/config/annotation/processor/AnnotationConfigTest.java index bd94c4bb1..7696781de 100644 --- a/config/config-annotation-processor/src/test/java/ru/tinkoff/kora/config/annotation/processor/AnnotationConfigTest.java +++ b/config/config-annotation-processor/src/test/java/ru/tinkoff/kora/config/annotation/processor/AnnotationConfigTest.java @@ -256,6 +256,11 @@ public boolean equals(Object obj) { } public int hashCode() { return java.util.Objects.hashCode(value); } + + @Override + public String toString() { + return "TestConfig[%s]".formatted(value); + } } """); @@ -343,4 +348,46 @@ public String toString() { .isEqualTo(expected); } + @Test + public void testPojoWithFluent() { + var extractor = this.compileConfig(List.of(), """ + @ru.tinkoff.kora.config.common.annotation.ConfigValueExtractor + public class TestConfig { + @Nullable + private final String value1; + @Nullable + private final String value2; + + public TestConfig(String value1, String value2) { + this.value1 = value1; + this.value2 = value2; + } + + public String value1() { + return this.value1; + } + + public String value2() { + return this.value2; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TestConfig that && java.util.Objects.equals(this.value1, that.value1) && java.util.Objects.equals(this.value2, that.value2); + } + + public int hashCode() { return java.util.Objects.hash(value1, value2); } + + public String toString() { + return "TestConfig(value1=%s, value2=%s)".formatted(this.value1, this.value2); + } + } + """); + + var expected = newObject("TestConfig", "test", null); + + assertThat(extractor.extract(MapConfigFactory.fromMap(Map.of("value1", "test")).root())) + .isEqualTo(expected); + } + } diff --git a/config/config-symbol-processor/src/main/kotlin/ru/tinkoff/kora/config/ksp/ConfigUtils.kt b/config/config-symbol-processor/src/main/kotlin/ru/tinkoff/kora/config/ksp/ConfigUtils.kt index 1fdc5bff5..71aaef932 100644 --- a/config/config-symbol-processor/src/main/kotlin/ru/tinkoff/kora/config/ksp/ConfigUtils.kt +++ b/config/config-symbol-processor/src/main/kotlin/ru/tinkoff/kora/config/ksp/ConfigUtils.kt @@ -44,7 +44,9 @@ object ConfigUtils { } var equals: KSFunctionDeclaration? = null var hashCode: KSFunctionDeclaration? = null + class FieldAndAccessors(var property: KSPropertyDeclaration? = null, var getter: KSFunctionDeclaration? = null, var setter: KSFunctionDeclaration? = null) + val propertyMap = hashMapOf() for (function in typeDecl.getDeclaredFunctions()) { val name = function.simpleName.asString() @@ -58,16 +60,17 @@ object ConfigUtils { } else { if (name.startsWith("get")) { val propertyName = name.substring(3).replaceFirstChar { it.lowercaseChar() } - propertyMap.computeIfAbsent(propertyName) {FieldAndAccessors()}.getter = function - } - if (name.startsWith("set")) { + propertyMap.computeIfAbsent(propertyName) { FieldAndAccessors() }.getter = function + } else if (name.startsWith("set")) { val propertyName = name.substring(3).replaceFirstChar { it.lowercaseChar() } - propertyMap.computeIfAbsent(propertyName) {FieldAndAccessors()}.setter = function + propertyMap.computeIfAbsent(propertyName) { FieldAndAccessors() }.setter = function + } else { + propertyMap.computeIfAbsent(name) { FieldAndAccessors() }.getter = function } } } for (property in typeDecl.getAllProperties()) { - propertyMap.computeIfAbsent(property.simpleName.asString()) {FieldAndAccessors()}.property = property + propertyMap.computeIfAbsent(property.simpleName.asString()) { FieldAndAccessors() }.property = property } val properties = propertyMap.values.asSequence().filter { it.setter != null && it.getter != null && it.property != null }.map { it.property!! }.toList() if (equals == null || hashCode == null) {