11/*
2- * Copyright (c) 2022, 2024 Oracle and/or its affiliates.
2+ * Copyright (c) 2022, 2025 Oracle and/or its affiliates.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
2121import java .io .UncheckedIOException ;
2222import java .nio .file .Files ;
2323import java .nio .file .Path ;
24+ import java .util .ArrayDeque ;
25+ import java .util .ArrayList ;
26+ import java .util .Deque ;
2427import java .util .List ;
28+ import java .util .ListIterator ;
2529import java .util .Map ;
2630import java .util .Optional ;
27- import java .util .Set ;
31+ import java .util .TreeMap ;
32+ import java .util .function .BiConsumer ;
2833import java .util .function .Function ;
2934import java .util .stream .Collectors ;
3035
@@ -123,7 +128,7 @@ public Optional<Config> asOptional() {
123128 }
124129
125130 /**
126- * Get the value as string.
131+ * Get the value as a string.
127132 *
128133 * @return optional
129134 */
@@ -132,7 +137,7 @@ public Optional<String> asString() {
132137 }
133138
134139 /**
135- * Get the value as boolean.
140+ * Get the value as a boolean.
136141 *
137142 * @return optional
138143 */
@@ -166,7 +171,7 @@ public <T> Optional<T> as(Function<Object, T> mapper) {
166171 * @return optional
167172 */
168173 public <T > Optional <T > as (Class <T > type ) {
169- return as (o -> cast (o , type ));
174+ return as (o -> convert (o , type ));
170175 }
171176
172177 /**
@@ -175,87 +180,99 @@ public <T> Optional<T> as(Class<T> type) {
175180 * @return optional
176181 */
177182 public Optional <List <Config >> asNodeList () {
178- return asList (e -> new Config (e , this ));
183+ if (value == null ) {
184+ return Optional .empty ();
185+ }
186+ if (value instanceof List ) {
187+ return Optional .of (((List <?>) value ).stream ()
188+ .map (e -> new Config (e , this ))
189+ .collect (Collectors .toList ()));
190+ }
191+ throw new MappingException (value .getClass (), List .class );
179192 }
180193
181194 /**
182- * Map the value to a list of object .
195+ * Get the value to a list.
183196 *
184197 * @return optional
185198 */
186- public Optional <List <Object >> asList () {
199+ public Optional <List <String >> asList () {
187200 return asList (Function .identity ());
188201 }
189202
190203 /**
191- * Map the value to a list of a given type .
204+ * Get the value as a list.
192205 *
193- * @param type requested type
194- * @param <T> requested type
206+ * @param type value type
207+ * @param <T> value type
195208 * @return optional
196209 */
197210 public <T > Optional <List <T >> asList (Class <T > type ) {
198- return asList (e -> cast (e , type ));
211+ return asList (e -> convert (e , type ));
199212 }
200213
201214 /**
202- * Map the value to a list using a mapping function .
215+ * Get the value as a list.
203216 *
204- * @param mapper mapping function
205- * @param <T> requested type
217+ * @param mapper value mapper
218+ * @param <T> value type
206219 * @return optional
207220 */
208- public <T > Optional <List <T >> asList (Function <Object , T > mapper ) {
221+ public <T > Optional <List <T >> asList (Function <String , T > mapper ) {
209222 if (value == null ) {
210223 return Optional .empty ();
211224 }
212225 if (value instanceof List ) {
213- return Optional .of (((List <?>) value ).stream ()
214- .map (mapper )
215- .collect (Collectors .toList ()));
226+ List <T > values = new ArrayList <>();
227+ traverse ((prefix , entry ) -> {
228+ T value = mapper .apply (entry .getValue ());
229+ values .add (value );
230+ });
231+ return Optional .of (values );
216232 }
217233 throw new MappingException (value .getClass (), List .class );
218234 }
219235
220236 /**
221- * Map the value to a map using a mapping function .
237+ * Get the value as a map.
222238 *
223- * @param mapper mapping function
224- * @param <T> requested type
225239 * @return optional
226240 */
227- public <T > Optional <Map <String , T >> asMap (Function <Object , T > mapper ) {
228- if (value == null ) {
229- return Optional .empty ();
230- }
231- if (value instanceof Map ) {
232- return Optional .of (((Map <?, ?>) value )
233- .entrySet ()
234- .stream ()
235- .map (e -> Map .entry (e .getKey ().toString (), mapper .apply (e .getValue ())))
236- .collect (Collectors .toMap (Map .Entry ::getKey , Map .Entry ::getValue )));
237- }
238- throw new MappingException (value .getClass (), Map .class );
241+ public Optional <Map <String , String >> asMap () {
242+ return asMap (Function .identity ());
239243 }
240244
241245 /**
242- * Map the value to a map of object .
246+ * Get the value as a map.
243247 *
248+ * @param type value type
249+ * @param <T> value type
244250 * @return optional
245251 */
246- public Optional <Map <String , Object >> asMap () {
247- return asMap (Function . identity ( ));
252+ public < T > Optional <Map <String , T >> asMap (Class < T > type ) {
253+ return asMap (e -> convert ( e , type ));
248254 }
249255
250256 /**
251- * Map the value to a map of a given type .
257+ * Get the value as a map.
252258 *
253- * @param type requested type
254- * @param <T> requested type
259+ * @param mapper value mapper
260+ * @param <T> value type
255261 * @return optional
256262 */
257- public <T > Optional <Map <String , T >> asMap (Class <T > type ) {
258- return asMap (e -> cast (e , type ));
263+ public <T > Optional <Map <String , T >> asMap (Function <String , T > mapper ) {
264+ if (value == null ) {
265+ return Optional .empty ();
266+ }
267+ if (value instanceof Map ) {
268+ Map <String , T > values = new TreeMap <>();
269+ traverse ((prefix , entry ) -> {
270+ String key = prefix .isEmpty () ? entry .getKey () : prefix + "." + entry .getKey ();
271+ values .put (key , mapper .apply (entry .getValue ()));
272+ });
273+ return Optional .of (values );
274+ }
275+ throw new MappingException (value .getClass (), Map .class );
259276 }
260277
261278 /**
@@ -308,31 +325,98 @@ public static Config create(Reader reader, Map<String, String> properties) {
308325 return create ((Object ) yaml .loadAs (reader , Object .class ), properties );
309326 }
310327
311- private static final Set <Class <?>> PRIMITIVE_BOXED =
312- Set .of (
313- Boolean .class ,
314- Character .class ,
315- Byte .class ,
316- Short .class ,
317- Integer .class ,
318- Long .class ,
319- Float .class ,
320- Double .class
321- );
322-
323-
324- private <T > T cast (Object obj , Class <T > type ) {
325- if (obj != null ) {
326- if (type .equals (String .class )
327- || obj .getClass ().isPrimitive ()
328- || PRIMITIVE_BOXED .contains (obj .getClass ())) {
329- return type .cast (substitutions .resolve (String .valueOf (obj )));
328+ private void traverse (BiConsumer <String , Map .Entry <String , String >> visitLeaf ) {
329+ Deque <String > path = new ArrayDeque <>();
330+ traverse ((k , v ) -> {
331+ if (k != null ) {
332+ path .addLast (substitutions .resolve (k ));
330333 }
331- if (type .isInstance (obj )) {
332- return type .cast (obj );
334+ }, (k , v ) -> {
335+ if (!path .isEmpty ()) {
336+ path .removeLast ();
337+ }
338+ }, (k , v ) -> {
339+ String prefix = String .join ("." , path );
340+ visitLeaf .accept (prefix , Map .entry (substitutions .resolve (k ), substitutions .resolve (v )));
341+ });
342+ }
343+
344+ private void traverse (BiConsumer <String , Object > visitNode ,
345+ BiConsumer <String , Object > postVisitNode ,
346+ BiConsumer <String , String > visitLeaf ) {
347+
348+ Deque <Object > parents = new ArrayDeque <>();
349+ Deque <String > keys = new ArrayDeque <>();
350+ Deque <Object > stack = new ArrayDeque <>();
351+ stack .push (value );
352+ while (!stack .isEmpty ()) {
353+ Object parent = parents .peek ();
354+ String key = keys .peek ();
355+ Object node = stack .peek ();
356+ if (parent == node ) {
357+ postVisitNode .accept (key , node );
358+ stack .pop ();
359+ if (!stack .isEmpty ()) {
360+ parents .pop ();
361+ keys .pop ();
362+ }
363+ } else if (node instanceof Map ) {
364+ List <? extends Map .Entry <?, ?>> entries = List .copyOf (((Map <?, ?>) node ).entrySet ());
365+ ListIterator <? extends Map .Entry <?, ?>> it = entries .listIterator (entries .size ());
366+ while (it .hasPrevious ()) {
367+ Map .Entry <?, ?> previous = it .previous ();
368+ stack .push (previous .getValue ());
369+ keys .push (previous .getKey ().toString ());
370+ }
371+ parents .push (node );
372+ visitNode .accept (key , node );
373+ } else if (node instanceof List ) {
374+ List <?> list = (List <?>) node ;
375+ ListIterator <?> it = list .listIterator (list .size ());
376+ while (it .hasPrevious ()) {
377+ keys .push (String .valueOf (it .previousIndex ()));
378+ stack .push (it .previous ());
379+ }
380+ parents .push (node );
381+ visitNode .accept (key , node );
382+ } else {
383+ visitLeaf .accept (key , String .valueOf (node ));
384+ stack .pop ();
385+ keys .pop ();
333386 }
334- throw new MappingException (obj .getClass (), type );
335387 }
336- return null ;
388+ }
389+
390+ @ SuppressWarnings ("unchecked" )
391+ private <T > T convert (Object obj , Class <T > type ) {
392+ String value = substitutions .resolve (String .valueOf (obj ));
393+ if (type .equals (String .class )) {
394+ return (T ) value ;
395+ }
396+ if (type .equals (Boolean .class ) || type .equals (boolean .class )) {
397+ return (T ) Boolean .valueOf (value );
398+ }
399+ if (type .equals (Character .class ) || type .equals (char .class )) {
400+ return (T ) Character .valueOf (value .charAt (0 ));
401+ }
402+ if (type .equals (Byte .class ) || type .equals (byte .class )) {
403+ return (T ) Byte .valueOf (value );
404+ }
405+ if (type .equals (Short .class ) || type .equals (short .class )) {
406+ return (T ) Short .valueOf (value );
407+ }
408+ if (type .equals (Integer .class ) || type .equals (int .class )) {
409+ return (T ) Integer .valueOf (value );
410+ }
411+ if (type .equals (Long .class ) || type .equals (long .class )) {
412+ return (T ) Long .valueOf (value );
413+ }
414+ if (type .equals (Float .class ) || type .equals (float .class )) {
415+ return (T ) Float .valueOf (value );
416+ }
417+ if (type .equals (Double .class ) || type .equals (double .class )) {
418+ return (T ) Double .valueOf (value );
419+ }
420+ throw new MappingException (obj .getClass (), type );
337421 }
338422}
0 commit comments