@@ -102,6 +102,80 @@ public static <V> void applyOverrides(V object, ValueSupplier supplier) {
102102 }
103103 }
104104 }
105+
106+ // Handle @Overwrite annotations on zero-parameter methods (getters).
107+ // When a getter like "greetingValue()" is annotated, the override is keyed by the method
108+ // name. Since we can't "set" a value through a getter, we need to find the backing field.
109+ // Strategy: look for a field whose name the method name starts with (e.g. "greetingValue"
110+ // starts with "greeting"), or try stripping common getter prefixes like "get"/"is".
111+ for (Method method : clazz .getDeclaredMethods ()) {
112+ if (method .getParameterCount () != 0 ) continue ;
113+ Optional <Object > override = supplier .getValue (method .getName ());
114+ if (override .isPresent ()) {
115+ Field targetField = findBackingField (clazz , method .getName (), method .getReturnType ());
116+ if (targetField != null ) {
117+ try {
118+ targetField .setAccessible (true );
119+ Object value = convertValue (targetField .getType (), override .get ());
120+ if (value != null ) {
121+ targetField .set (object , value );
122+ }
123+ } catch (IllegalAccessException e ) {
124+ log .warn ("Could not set override for getter {}: {}" , method .getName (), e .getMessage ());
125+ } catch (NumberFormatException e ) {
126+ log .warn ("Could not convert override value for getter {}: {}" , method .getName (), e .getMessage ());
127+ }
128+ } else {
129+ log .warn ("Could not find backing field for getter method {}" , method .getName ());
130+ }
131+ }
132+ }
133+ }
134+
135+ /**
136+ * Finds the backing field for a getter method by trying several strategies:
137+ * <ol>
138+ * <li>Exact name match (method name equals field name)</li>
139+ * <li>JavaBean getter convention: strip "get"/"is" prefix and lowercase first char</li>
140+ * <li>Prefix match: find a field whose name the method name starts with,
141+ * preferring the longest matching field name (e.g. "greetingValue" matches "greeting")</li>
142+ * </ol>
143+ * Only fields whose type is compatible with the method's return type are considered.
144+ */
145+ private static Field findBackingField (Class <?> clazz , String methodName , Class <?> returnType ) {
146+ // Strategy 1: exact name match
147+ for (Field field : clazz .getDeclaredFields ()) {
148+ if (field .getName ().equals (methodName ) && field .getType ().equals (returnType )) {
149+ return field ;
150+ }
151+ }
152+
153+ // Strategy 2: JavaBean getter convention (getHost -> host, isDebug -> debug)
154+ String beanFieldName = null ;
155+ if (methodName .startsWith ("get" ) && methodName .length () > 3 ) {
156+ beanFieldName = Character .toLowerCase (methodName .charAt (3 )) + methodName .substring (4 );
157+ } else if (methodName .startsWith ("is" ) && methodName .length () > 2 ) {
158+ beanFieldName = Character .toLowerCase (methodName .charAt (2 )) + methodName .substring (3 );
159+ }
160+ if (beanFieldName != null ) {
161+ for (Field field : clazz .getDeclaredFields ()) {
162+ if (field .getName ().equals (beanFieldName ) && field .getType ().equals (returnType )) {
163+ return field ;
164+ }
165+ }
166+ }
167+
168+ // Strategy 3: find the field whose name is the longest prefix of the method name
169+ Field bestMatch = null ;
170+ int bestLength = 0 ;
171+ for (Field field : clazz .getDeclaredFields ()) {
172+ String fname = field .getName ();
173+ if (methodName .startsWith (fname ) && fname .length () > bestLength && field .getType ().equals (returnType )) {
174+ bestMatch = field ;
175+ bestLength = fname .length ();
176+ }
177+ }
178+ return bestMatch ;
105179 }
106180
107181 /**
0 commit comments