Skip to content

Commit ea32188

Browse files
authored
Fluent accessor support for pojo config classes (#203)
1 parent b259e4f commit ea32188

File tree

3 files changed

+67
-12
lines changed

3 files changed

+67
-12
lines changed

config/config-annotation-processor/src/main/java/ru/tinkoff/kora/config/annotation/processor/ConfigUtils.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,14 @@ class FieldAndAccessors {
151151
equals = method;
152152
} else if (name.equals("hashCode") && method.getParameters().isEmpty()) {
153153
hashCode = method;
154-
} else {
154+
} else if (method.getParameters().isEmpty()) {
155155
if (name.startsWith("get")) {
156156
fieldsWithAccessors.computeIfAbsent(CommonUtils.decapitalize(name.substring(3)), n -> new FieldAndAccessors()).getter = method;
157-
} else if (name.startsWith("set")) {
158-
fieldsWithAccessors.computeIfAbsent(CommonUtils.decapitalize(name.substring(3)), n -> new FieldAndAccessors()).setter = method;
157+
} else {
158+
fieldsWithAccessors.computeIfAbsent(name, n -> new FieldAndAccessors()).getter = method;
159159
}
160+
} else if (method.getParameters().size() == 1 && name.startsWith("set")) {
161+
fieldsWithAccessors.computeIfAbsent(CommonUtils.decapitalize(name.substring(3)), n -> new FieldAndAccessors()).setter = method;
160162
}
161163
}
162164
}
@@ -167,24 +169,27 @@ class FieldAndAccessors {
167169
var constructors = CommonUtils.findConstructors(te, m -> m.contains(Modifier.PUBLIC));
168170
var emptyConstructor = constructors.stream().filter(e -> e.getParameters().isEmpty()).findFirst().orElse(null);
169171
var nonEmptyConstructor = constructors.stream().filter(e -> !e.getParameters().isEmpty()).findFirst().orElse(null);
170-
var constructorParams = nonEmptyConstructor == null ? Set.of() : nonEmptyConstructor.getParameters().stream().map(VariableElement::getSimpleName).map(Objects::toString).collect(Collectors.toSet());
172+
var constructorParams = nonEmptyConstructor == null ? Map.of() : nonEmptyConstructor.getParameters().stream().collect(Collectors.toMap(
173+
p -> p.getSimpleName().toString(),
174+
p -> p
175+
));
171176

172177
var seen = new HashSet<String>();
173178
var fields = new ArrayList<ConfigField>();
174179
for (var fieldWithAccessors : fieldsWithAccessors.entrySet()) {
175180
var name = fieldWithAccessors.getKey();
176181
var value = fieldWithAccessors.getValue();
177-
if (value.getter == null) {
182+
if (value.getter == null || value.field == null) {
178183
continue;
179184
}
180-
if (value.setter == null && !constructorParams.contains(value.field.getSimpleName().toString())) {
185+
if (value.setter == null && !constructorParams.containsKey(value.field.getSimpleName().toString())) {
181186
continue;
182187
}
183188
var fieldType = types.asMemberOf(typeMirror, value.field);
184189
if (seen.add(name)) {
185190
var isNullable = CommonUtils.isNullable(value.field) && !fieldType.getKind().isPrimitive();
186191
var mapping = CommonUtils.parseMapping(value.field).getMapping(ConfigClassNames.configValueExtractor);
187-
var hasDefault = emptyConstructor != null || !constructorParams.contains(value.field.getSimpleName().toString());
192+
var hasDefault = emptyConstructor != null || !constructorParams.containsKey(value.field.getSimpleName().toString());
188193
fields.add(new ConfigUtils.ConfigField(
189194
name, TypeName.get(fieldType), isNullable, hasDefault, mapping
190195
));

config/config-annotation-processor/src/test/java/ru/tinkoff/kora/config/annotation/processor/AnnotationConfigTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,11 @@ public boolean equals(Object obj) {
256256
}
257257
258258
public int hashCode() { return java.util.Objects.hashCode(value); }
259+
260+
@Override
261+
public String toString() {
262+
return "TestConfig[%s]".formatted(value);
263+
}
259264
}
260265
""");
261266

@@ -343,4 +348,46 @@ public String toString() {
343348
.isEqualTo(expected);
344349
}
345350

351+
@Test
352+
public void testPojoWithFluent() {
353+
var extractor = this.compileConfig(List.of(), """
354+
@ru.tinkoff.kora.config.common.annotation.ConfigValueExtractor
355+
public class TestConfig {
356+
@Nullable
357+
private final String value1;
358+
@Nullable
359+
private final String value2;
360+
361+
public TestConfig(String value1, String value2) {
362+
this.value1 = value1;
363+
this.value2 = value2;
364+
}
365+
366+
public String value1() {
367+
return this.value1;
368+
}
369+
370+
public String value2() {
371+
return this.value2;
372+
}
373+
374+
@Override
375+
public boolean equals(Object obj) {
376+
return obj instanceof TestConfig that && java.util.Objects.equals(this.value1, that.value1) && java.util.Objects.equals(this.value2, that.value2);
377+
}
378+
379+
public int hashCode() { return java.util.Objects.hash(value1, value2); }
380+
381+
public String toString() {
382+
return "TestConfig(value1=%s, value2=%s)".formatted(this.value1, this.value2);
383+
}
384+
}
385+
""");
386+
387+
var expected = newObject("TestConfig", "test", null);
388+
389+
assertThat(extractor.extract(MapConfigFactory.fromMap(Map.of("value1", "test")).root()))
390+
.isEqualTo(expected);
391+
}
392+
346393
}

config/config-symbol-processor/src/main/kotlin/ru/tinkoff/kora/config/ksp/ConfigUtils.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ object ConfigUtils {
4444
}
4545
var equals: KSFunctionDeclaration? = null
4646
var hashCode: KSFunctionDeclaration? = null
47+
4748
class FieldAndAccessors(var property: KSPropertyDeclaration? = null, var getter: KSFunctionDeclaration? = null, var setter: KSFunctionDeclaration? = null)
49+
4850
val propertyMap = hashMapOf<String, FieldAndAccessors>()
4951
for (function in typeDecl.getDeclaredFunctions()) {
5052
val name = function.simpleName.asString()
@@ -58,16 +60,17 @@ object ConfigUtils {
5860
} else {
5961
if (name.startsWith("get")) {
6062
val propertyName = name.substring(3).replaceFirstChar { it.lowercaseChar() }
61-
propertyMap.computeIfAbsent(propertyName) {FieldAndAccessors()}.getter = function
62-
}
63-
if (name.startsWith("set")) {
63+
propertyMap.computeIfAbsent(propertyName) { FieldAndAccessors() }.getter = function
64+
} else if (name.startsWith("set")) {
6465
val propertyName = name.substring(3).replaceFirstChar { it.lowercaseChar() }
65-
propertyMap.computeIfAbsent(propertyName) {FieldAndAccessors()}.setter = function
66+
propertyMap.computeIfAbsent(propertyName) { FieldAndAccessors() }.setter = function
67+
} else {
68+
propertyMap.computeIfAbsent(name) { FieldAndAccessors() }.getter = function
6669
}
6770
}
6871
}
6972
for (property in typeDecl.getAllProperties()) {
70-
propertyMap.computeIfAbsent(property.simpleName.asString()) {FieldAndAccessors()}.property = property
73+
propertyMap.computeIfAbsent(property.simpleName.asString()) { FieldAndAccessors() }.property = property
7174
}
7275
val properties = propertyMap.values.asSequence().filter { it.setter != null && it.getter != null && it.property != null }.map { it.property!! }.toList()
7376
if (equals == null || hashCode == null) {

0 commit comments

Comments
 (0)