77 */
88package org .seedstack .seed .core .internal .configuration .tool ;
99
10+ import org .seedstack .coffig .Coffig ;
1011import org .seedstack .coffig .Config ;
1112import org .seedstack .coffig .SingleValue ;
1213import org .seedstack .shed .reflect .Annotations ;
14+ import org .seedstack .shed .reflect .ReflectUtils ;
15+ import org .seedstack .shed .reflect .Types ;
1316
17+ import javax .annotation .Nullable ;
1418import javax .validation .constraints .NotNull ;
1519import java .lang .reflect .Field ;
1620import java .lang .reflect .Modifier ;
21+ import java .lang .reflect .ParameterizedType ;
22+ import java .lang .reflect .Type ;
1723import java .util .ArrayList ;
1824import java .util .Arrays ;
1925import java .util .Collection ;
2430import java .util .Map ;
2531import java .util .MissingResourceException ;
2632import java .util .ResourceBundle ;
27- import java .util .SortedMap ;
2833import java .util .TreeMap ;
2934
3035import static org .seedstack .shed .reflect .Classes .instantiateDefault ;
3136import static org .seedstack .shed .reflect .ReflectUtils .makeAccessible ;
3237import static org .seedstack .shed .reflect .Types .simpleNameOf ;
3338
3439class Node implements Comparable <Node > {
35- private final String name ;
3640 private final Class <?> configClass ;
41+ private final Coffig coffig ;
42+ private final String name ;
3743 private final Class <?> outermostClass ;
3844 private final int outermostLevel ;
3945 private final String [] path ;
46+ private final ResourceBundle bundle ;
4047 private final Map <String , PropertyInfo > propertyInfo ;
41- private final SortedMap <String , Node > children = new TreeMap <>();
48+ private final Map <String , Node > children = new TreeMap <>();
4249
4350 Node () {
44- this .name = "" ;
4551 this .configClass = null ;
52+ this .coffig = null ;
53+ this .name = "" ;
4654 this .outermostClass = null ;
4755 this .outermostLevel = 0 ;
4856 this .path = new String [0 ];
57+ this .bundle = null ;
4958 this .propertyInfo = new HashMap <>();
5059 }
5160
52- Node (Class <?> configClass ) {
61+ Node (Class <?> configClass , Coffig coffig ) {
5362 this .configClass = configClass ;
63+ this .coffig = coffig ;
5464
5565 List <String > path = new ArrayList <>();
5666 Class <?> previousClass = configClass ;
57- int nestingLevel = -1 ;
5867 do {
5968 Config annotation = configClass .getAnnotation (Config .class );
6069 if (annotation == null ) {
@@ -64,15 +73,15 @@ class Node implements Comparable<Node> {
6473 Collections .reverse (splitPath );
6574 path .addAll (splitPath );
6675 previousClass = configClass ;
67- nestingLevel ++;
6876 } while ((configClass = configClass .getDeclaringClass ()) != null );
6977
7078 Collections .reverse (path );
7179 this .outermostClass = previousClass ;
7280 this .outermostLevel = this .outermostClass .getAnnotation (Config .class ).value ().split ("\\ ." ).length ;
7381 this .path = path .toArray (new String [path .size ()]);
7482 this .name = this .path [this .path .length - 1 ];
75- this .propertyInfo = buildPropertyInfo ();
83+ this .bundle = getResourceBundle ();
84+ this .propertyInfo = buildPropertyInfo (this .configClass , "" , null );
7685 }
7786
7887 String getName () {
@@ -149,68 +158,86 @@ public int compareTo(Node that) {
149158 return 0 ;
150159 }
151160
152- private Map <String , PropertyInfo > buildPropertyInfo () {
161+ private Map <String , PropertyInfo > buildPropertyInfo (Class <?> configClass , String parentPropertyName , Object defaultInstance ) {
153162 Map <String , PropertyInfo > result = new LinkedHashMap <>();
154163
155- ResourceBundle bundle = null ;
156- try {
157- bundle = ResourceBundle .getBundle (outermostClass .getName ());
158- } catch (MissingResourceException e ) {
159- // ignore
160- }
161-
162- Object defaultInstance ;
163- try {
164- defaultInstance = instantiateDefault (configClass );
165- } catch (Exception e ) {
166- defaultInstance = null ;
164+ if (defaultInstance == null ) {
165+ try {
166+ defaultInstance = instantiateDefault (configClass );
167+ } catch (Exception e ) {
168+ defaultInstance = null ;
169+ }
167170 }
168171
169172 for (Field field : configClass .getDeclaredFields ()) {
170173 if (Modifier .isStatic (field .getModifiers ())) {
174+ // Skip static fields (not used for configuration)
171175 continue ;
172176 }
173177 if (field .getType ().isAnnotationPresent (Config .class )) {
178+ // Skip fields of type annotated with @Config as they are already detected
174179 continue ;
175180 }
176181
177182 makeAccessible (field );
178183
179- PropertyInfo propertyInfo = new PropertyInfo ();
180184 Config configAnnotation = field .getAnnotation (Config .class );
185+ Type genericType = field .getGenericType ();
181186 String name ;
182187 if (configAnnotation != null ) {
183188 name = configAnnotation .value ();
184189 } else {
185190 name = field .getName ();
186191 }
187192
193+ PropertyInfo propertyInfo = new PropertyInfo ();
188194 propertyInfo .setName (name );
189- propertyInfo .setShortDescription (getMessage (bundle , "No description. " , buildKey (name )));
190- propertyInfo .setLongDescription (getMessage (bundle , null , buildKey (name , "long" )));
191- propertyInfo .setType (simpleNameOf (field . getGenericType () ));
195+ propertyInfo .setShortDescription (getMessage (" " , buildKey (parentPropertyName , name )));
196+ propertyInfo .setLongDescription (getMessage (null , buildKey (parentPropertyName , name , "long" )));
197+ propertyInfo .setType (simpleNameOf (genericType ));
192198 propertyInfo .setSingleValue (field .isAnnotationPresent (SingleValue .class ));
199+ propertyInfo .setMandatory (isNotNull (field ));
193200 if (defaultInstance != null ) {
194- try {
195- propertyInfo .setDefaultValue (field .get (defaultInstance ));
196- } catch (IllegalAccessException e ) {
197- // ignore
198- }
201+ propertyInfo .setDefaultValue (ReflectUtils .getValue (field , defaultInstance ));
202+ }
203+
204+ Class <?> rawClass = Types .rawClassOf (genericType );
205+ Type itemType ;
206+ if (Collection .class .isAssignableFrom (rawClass ) && genericType instanceof ParameterizedType ) {
207+ itemType = Types .rawClassOf (((ParameterizedType ) genericType ).getActualTypeArguments ()[0 ]);
208+ } else if (Map .class .isAssignableFrom (rawClass ) && genericType instanceof ParameterizedType ) {
209+ itemType = Types .rawClassOf (((ParameterizedType ) genericType ).getActualTypeArguments ()[1 ]);
210+ } else if (genericType instanceof Class <?> && ((Class <?>) genericType ).isArray ()) {
211+ itemType = ((Class <?>) genericType ).getComponentType ();
212+ } else {
213+ itemType = genericType ;
214+ }
215+ if (!coffig .getMapper ().canHandle (itemType )) {
216+ propertyInfo .addInnerPropertyInfo (
217+ buildPropertyInfo (
218+ Types .rawClassOf (itemType ),
219+ parentPropertyName .isEmpty () ? name : parentPropertyName + "." + name ,
220+ itemType .equals (genericType ) ? ReflectUtils .getValue (field , defaultInstance ) : null
221+ )
222+ );
199223 }
200- propertyInfo .setMandatory (propertyInfo .getDefaultValue () == null && Annotations .on (field ).includingMetaAnnotations ().find (NotNull .class ).isPresent ());
201224
202225 result .put (name , propertyInfo );
203226 }
204227
205228 return result ;
206229 }
207230
208- private String getMessage (ResourceBundle resourceBundle , String defaultMessage , String key ) {
209- if (resourceBundle == null ) {
231+ private boolean isNotNull (Field field ) {
232+ return Annotations .on (field ).includingMetaAnnotations ().find (NotNull .class ).isPresent ();
233+ }
234+
235+ private String getMessage (String defaultMessage , String key ) {
236+ if (bundle == null ) {
210237 return defaultMessage ;
211238 }
212239 try {
213- return resourceBundle .getString (key );
240+ return bundle .getString (key );
214241 } catch (MissingResourceException e ) {
215242 return defaultMessage ;
216243 }
@@ -227,11 +254,24 @@ private String buildKey(String... parts) {
227254 }
228255 }
229256 for (int i = 0 ; i < parts .length ; i ++) {
230- sb .append (parts [i ]);
231- if (i < parts .length - 1 ) {
232- sb .append ("." );
257+ if (!parts [i ].isEmpty ()) {
258+ sb .append (parts [i ]);
259+ if (i < parts .length - 1 ) {
260+ sb .append ("." );
261+ }
233262 }
234263 }
235264 return sb .toString ();
236265 }
266+
267+ @ Nullable
268+ private ResourceBundle getResourceBundle () {
269+ ResourceBundle bundle ;
270+ try {
271+ bundle = ResourceBundle .getBundle (outermostClass .getName ());
272+ } catch (MissingResourceException e ) {
273+ bundle = null ;
274+ }
275+ return bundle ;
276+ }
237277}
0 commit comments