Skip to content

Commit 81286de

Browse files
Add support for array, List, and Set overrides with tests for type conversion.
Signed-off-by: Nora <46890129+RainbowDashLabs@users.noreply.github.com>
1 parent 2133ac3 commit 81286de

3 files changed

Lines changed: 124 additions & 5 deletions

File tree

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ plugins {
1212

1313
publishData {
1414
useEldoNexusRepos(false)
15-
publishingVersion = "2.1.4"
15+
publishingVersion = "2.2.0"
1616
}
1717

1818
group = "dev.chojo"

src/main/java/dev/chojo/ocular/override/OverrideApplier.java

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,16 @@
77

88
import org.slf4j.Logger;
99

10+
import java.lang.reflect.Array;
1011
import java.lang.reflect.Field;
1112
import java.lang.reflect.Method;
13+
import java.lang.reflect.ParameterizedType;
14+
import java.lang.reflect.Type;
15+
import java.util.ArrayList;
16+
import java.util.HashSet;
17+
import java.util.List;
1218
import java.util.Optional;
19+
import java.util.Set;
1320

1421
import static org.slf4j.LoggerFactory.getLogger;
1522

@@ -68,7 +75,7 @@ public static <V> void applyOverrides(V object, ValueSupplier supplier) {
6875
field.setAccessible(true);
6976
// The override value comes as a String (from env / prop), but the field
7077
// might be an int, boolean, etc. convertValue handles that conversion.
71-
Object value = convertValue(field.getType(), override.get());
78+
Object value = convertValue(field.getType(), field.getGenericType(), override.get());
7279
if (value != null) {
7380
// Actually write the override value into the field on the config object
7481
field.set(object, value);
@@ -91,7 +98,7 @@ public static <V> void applyOverrides(V object, ValueSupplier supplier) {
9198
try {
9299
method.setAccessible(true);
93100
// Convert the string value to match the method's parameter type
94-
Object value = convertValue(method.getParameterTypes()[0], override.get());
101+
Object value = convertValue(method.getParameterTypes()[0], method.getGenericParameterTypes()[0], override.get());
95102
if (value != null) {
96103
// Call the method on the config object, passing the override value
97104
method.invoke(object, value);
@@ -119,7 +126,7 @@ public static <V> void applyOverrides(V object, ValueSupplier supplier) {
119126
if (targetField != null) {
120127
try {
121128
targetField.setAccessible(true);
122-
Object value = convertValue(targetField.getType(), override.get());
129+
Object value = convertValue(targetField.getType(), targetField.getGenericType(), override.get());
123130
if (value != null) {
124131
targetField.set(object, value);
125132
}
@@ -253,9 +260,11 @@ private static void logOverwriteSources(Overwrite overwrite, String prefix, bool
253260
/**
254261
* Converts a string override value to the target field/parameter type.
255262
* Supports all Java primitive types and their boxed equivalents, plus String.
263+
* Also supports arrays, {@link List} and {@link Set} of String, where the input
264+
* value is a comma-separated string.
256265
* Returns null for unsupported types (with a warning logged).
257266
*/
258-
private static Object convertValue(Class<?> type, Object value) {
267+
private static Object convertValue(Class<?> type, Type genericType, Object value) {
259268
if (value == null) return null;
260269
// Environment variables and system properties are always strings, so we need to parse
261270
// them into the correct Java type. For example, the string "8080" becomes the int 8080.
@@ -270,6 +279,44 @@ private static Object convertValue(Class<?> type, Object value) {
270279
if (type == short.class || type == Short.class) return Short.parseShort(stringValue);
271280
if (type == byte.class || type == Byte.class) return Byte.parseByte(stringValue);
272281

282+
// Arrays, Lists and Sets are provided as comma-separated strings
283+
if (type.isArray()) {
284+
Class<?> componentType = type.getComponentType();
285+
String[] parts = stringValue.split(",", -1);
286+
Object array = Array.newInstance(componentType, parts.length);
287+
for (int i = 0; i < parts.length; i++) {
288+
Object element = convertValue(componentType, componentType, parts[i].trim());
289+
Array.set(array, i, element);
290+
}
291+
return array;
292+
}
293+
294+
// Resolve the element type from the generic type parameter (e.g. List<Integer> -> Integer)
295+
Class<?> elementType = String.class;
296+
if (genericType instanceof ParameterizedType parameterized) {
297+
Type[] typeArgs = parameterized.getActualTypeArguments();
298+
if (typeArgs.length == 1 && typeArgs[0] instanceof Class<?> cls) {
299+
elementType = cls;
300+
}
301+
}
302+
303+
if (type == List.class || type == ArrayList.class) {
304+
String[] parts = stringValue.split(",", -1);
305+
List<Object> list = new ArrayList<>();
306+
for (String part : parts) {
307+
list.add(convertValue(elementType, elementType, part.trim()));
308+
}
309+
return list;
310+
}
311+
if (type == Set.class || type == HashSet.class) {
312+
String[] parts = stringValue.split(",", -1);
313+
Set<Object> set = new HashSet<>();
314+
for (String part : parts) {
315+
set.add(convertValue(elementType, elementType, part.trim()));
316+
}
317+
return set;
318+
}
319+
273320
log.warn("Unsupported override type: {}", type.getName());
274321
return null;
275322
}

src/test/java/dev/chojo/ocular/OverrideApplierTest.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99
import dev.chojo.ocular.override.ValueSupplier;
1010
import org.junit.jupiter.api.Test;
1111

12+
import java.util.List;
1213
import java.util.Map;
1314
import java.util.Optional;
15+
import java.util.Set;
1416

17+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
1518
import static org.junit.jupiter.api.Assertions.assertEquals;
1619
import static org.junit.jupiter.api.Assertions.assertTrue;
1720

@@ -107,6 +110,75 @@ void applierUsesSupplierValue() {
107110
assertEquals("supplied", target.name);
108111
}
109112

113+
@Test
114+
void stringArrayOverride() {
115+
CollectionTarget target = new CollectionTarget();
116+
MapSupplier supplier = new MapSupplier(Map.of("tags", "a,b,c"));
117+
118+
OverrideApplier.applyOverrides(target, supplier);
119+
120+
assertArrayEquals(new String[]{"a", "b", "c"}, target.tags);
121+
}
122+
123+
@Test
124+
void intArrayOverride() {
125+
CollectionTarget target = new CollectionTarget();
126+
MapSupplier supplier = new MapSupplier(Map.of("numbers", "1,2,3"));
127+
128+
OverrideApplier.applyOverrides(target, supplier);
129+
130+
assertArrayEquals(new int[]{1, 2, 3}, target.numbers);
131+
}
132+
133+
@Test
134+
void listOverride() {
135+
CollectionTarget target = new CollectionTarget();
136+
MapSupplier supplier = new MapSupplier(Map.of("names", "alice,bob,charlie"));
137+
138+
OverrideApplier.applyOverrides(target, supplier);
139+
140+
assertEquals(List.of("alice", "bob", "charlie"), target.names);
141+
}
142+
143+
@Test
144+
void listOfIntegersOverride() {
145+
CollectionTarget target = new CollectionTarget();
146+
MapSupplier supplier = new MapSupplier(Map.of("counts", "10,20,30"));
147+
148+
OverrideApplier.applyOverrides(target, supplier);
149+
150+
assertEquals(List.of(10, 20, 30), target.counts);
151+
}
152+
153+
@Test
154+
void setOverride() {
155+
CollectionTarget target = new CollectionTarget();
156+
MapSupplier supplier = new MapSupplier(Map.of("roles", "admin,user,guest"));
157+
158+
OverrideApplier.applyOverrides(target, supplier);
159+
160+
assertEquals(Set.of("admin", "user", "guest"), target.roles);
161+
}
162+
163+
@Test
164+
void setOfIntegersOverride() {
165+
CollectionTarget target = new CollectionTarget();
166+
MapSupplier supplier = new MapSupplier(Map.of("ids", "1,2,3"));
167+
168+
OverrideApplier.applyOverrides(target, supplier);
169+
170+
assertEquals(Set.of(1, 2, 3), target.ids);
171+
}
172+
173+
static class CollectionTarget {
174+
String[] tags = {};
175+
int[] numbers = {};
176+
List<String> names = List.of();
177+
List<Integer> counts = List.of();
178+
Set<String> roles = Set.of();
179+
Set<Integer> ids = Set.of();
180+
}
181+
110182
static class FieldTarget {
111183
String name = "original";
112184
int count = 0;

0 commit comments

Comments
 (0)