1010import me .zort .configurationlib .annotation .ThisNodeId ;
1111import me .zort .configurationlib .util .NodeTypeToken ;
1212import me .zort .configurationlib .util .Placeholders ;
13+ import me .zort .configurationlib .util .ReflectionHelper ;
1314import me .zort .configurationlib .util .Validator ;
1415import org .apache .commons .lang .ArrayUtils ;
1516import org .jetbrains .annotations .ApiStatus ;
3536public abstract class SectionNode <L > implements Node <L > {
3637
3738 private final Map <Class <?>, NodeAdapter <?, L >> adapters = new ConcurrentHashMap <>();
38- @ Nullable
39+
40+ @ Getter (onMethod_ = {@ Nullable })
3941 private final SectionNode <L > parent ;
4042 private LogAdapter logAdapter ;
4143 @ Setter
@@ -55,11 +57,10 @@ public SectionNode(@Nullable SectionNode<L> parent, LogAdapter logAdapter) {
5557 }
5658
5759 @ ApiStatus .Internal
58- public abstract Node <L > createNode (String key , Object value , NodeTypeToken <?> type );
60+ public abstract Node <L > createNode (String key , @ Nullable Object value , NodeTypeToken <?> type );
5961 // Key is always definitive.
6062 public abstract void deleteNode (String key );
6163 public abstract void set (String key , Node <L > node );
62- public abstract Node <L > get (String path );
6364 public abstract Collection <Node <L >> getNodes ();
6465
6566 public void clear () {
@@ -100,6 +101,15 @@ public void unregisterAdapter(Class<?> type) {
100101 adapters .remove (type );
101102 }
102103
104+ /**
105+ * Creates and sets new section to the source.
106+ *
107+ * @param key The key/path of the section.
108+ */
109+ public void createSection (String key ) {
110+ set (key , createNode (key , null , NodeTypes .SECTION ));
111+ }
112+
103113 /**
104114 * Updates this node's values from the provided mapped
105115 * object.
@@ -129,7 +139,7 @@ public void set(Object from) {
129139 content .forEach (this ::set );
130140 }
131141
132- public void set (String key , Object value ) {
142+ public void set (String key , @ Nullable Object value ) {
133143 if (isContextDebug () && isHighestLevel ()) {
134144 doLog ("Highest level inspection:" );
135145 doLog ("Before update:" );
@@ -187,17 +197,40 @@ public <T> T map(Class<T> typeClass) {
187197 return map (typeClass , new Placeholders ());
188198 }
189199
200+ @ SuppressWarnings ("rawtypes, unchecked" )
190201 public <T > T map (Class <T > typeClass , Placeholders placeholders ) {
191202 try {
192203 if (Primitives .isWrapperType (Primitives .wrap (typeClass ))) {
193204 // We cannot map sections to primitive types since
194205 // sections are not leaf nodes.
195206 return null ;
196207 }
197- Constructor <T > declaredConstructor = typeClass .getDeclaredConstructor ();
198- declaredConstructor .setAccessible (true );
199- return map (declaredConstructor .newInstance (), placeholders );
200- } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e ) {
208+ try {
209+ Constructor <T > declaredConstructor = typeClass .getDeclaredConstructor ();
210+ declaredConstructor .setAccessible (true );
211+ return map (declaredConstructor .newInstance (), placeholders );
212+ } catch (NoSuchMethodException e1 ) {
213+ T instance = null ;
214+
215+ try {
216+ // Use custom pre-build strategy first to assure correct
217+ // object creation.
218+ NodeDeserializer deserializer = obtainAdapter (typeClass , NodeDeserializer .class );
219+ Object tempPreBuiltInstance ;
220+ if (deserializer != null && (tempPreBuiltInstance = deserializer .preBuildInstance (typeClass , getContext (), placeholders )) != null ) {
221+ instance = (T ) tempPreBuiltInstance ;
222+ }
223+ } catch (Exception e2 ) {
224+ debug (e2 .getMessage ());
225+ if (isContextDebug ()) e2 .printStackTrace ();
226+ }
227+
228+ if (instance == null )
229+ throw new RuntimeException ("Cannot create instance of " + typeClass .getName () + "!" );
230+
231+ return map (instance , placeholders );
232+ }
233+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e ) {
201234 throw new RuntimeException (e );
202235 }
203236 }
@@ -222,18 +255,20 @@ public <T> T map(T obj, Placeholders placeholders) {
222255 public <T > T map (T obj , Placeholders placeholders , boolean useCustomAdapters ) {
223256 Class <?> typeClass = obj .getClass ();
224257 if (Primitives .isWrapperType (Primitives .wrap (typeClass ))) {
258+ debug (String .format ("Cannot map section to primitive type for %s" , typeClass .getName ()));
225259 return null ;
226260 }
227261
228262 NodeDeserializer nodeDeserializer = useCustomAdapters ? obtainAdapter (obj , NodeDeserializer .class ) : null ;
229- if (nodeDeserializer != null ) {
263+ if (nodeDeserializer != null ) {
230264 NodeContext <Node <L >, L > context = getContext ();
231265 Object deserialized = nodeDeserializer .deserialize (obj , context , placeholders );
232266 if (!obj .getClass ().isAssignableFrom (deserialized .getClass ())) {
233267 throw new RuntimeException (String .format ("Deserialized object is not assignable to the provided type! (%s -> %s)" ,
234268 obj .getClass ().getName (),
235269 deserialized .getClass ().getName ()));
236270 }
271+ debug (String .format ("Deserialized object %s using adapter %s" , deserialized , nodeDeserializer .getClass ().getName ()));
237272 return (T ) deserialized ;
238273 }
239274
@@ -243,13 +278,16 @@ public <T> T map(T obj, Placeholders placeholders, boolean useCustomAdapters) {
243278 for (Field field : typeClass .getDeclaredFields ()) {
244279 if (Modifier .isTransient (field .getModifiers ())) {
245280 // Transient fields are skipped.
281+ debug (String .format ("Skipping transient field %s" , field .getName ()));
246282 continue ;
247283 }
248284 field .setAccessible (true );
249285 Object value = Defaults .defaultValue (field .getType ());
250286 if (String .class .equals (field .getType ()) && field .isAnnotationPresent (ThisNodeId .class )) {
251287 value = getName ();
288+ debug (String .format ("Field %s is mapped to this node id %s" , field .getName (), value ));
252289 } else if (nodeCandidates .containsKey (field .getName ())) {
290+ debug ("Found node for field " + field .getName ());
253291 Object builtValue = buildValue (field , nodeCandidates .get (field .getName ()), placeholders );
254292 if (builtValue != null ) {
255293 value = builtValue ;
@@ -286,25 +324,58 @@ public <T> T map(T obj, Placeholders placeholders, boolean useCustomAdapters) {
286324 public Object buildValue (Field field , Node <L > node , Placeholders placeholders ) {
287325 Object value = null ;
288326
327+ debug ("Building value for field " + field .getName ());
328+
289329 if (node instanceof SimpleNode && isPrimitive (field .getType ())) {
290330 value = ((SimpleNode <L >) node ).get ();
331+ debug (String .format ("Field %s is mapped to simple node %s" , field .getName (), value ));
291332 } else if (node instanceof SectionNode ) {
292333 Class <?> contentType ;
293334 if (List .class .isAssignableFrom (field .getType ())
294335 && !isPrimitive (contentType = (Class <?>) ((ParameterizedType ) field .getGenericType ()).getActualTypeArguments ()[0 ])) {
295336 List list = new ArrayList ();
296337 ((SectionNode <Object >) node ).getNodes (NodeTypes .SECTION )
297338 .forEach (sn -> {
298- list .add (sn .map (contentType ));
339+ if (SectionNode .class .isAssignableFrom (contentType )) {
340+ list .add (sn );
341+ } else {
342+ list .add (sn .map (contentType ));
343+ }
299344 });
345+ debug (String .format ("Field %s is mapped to list of sections %s" , field .getName (), list ));
300346 return list ;
347+ } else if (SectionNode .class .isAssignableFrom (field .getType ())) {
348+ value = node ;
349+ debug (String .format ("Field %s is mapped to section node %s" , field .getName (), value ));
301350 } else {
302351 value = ((SectionNode <L >) node ).map (field .getType ());
352+ debug (String .format ("Field %s is mapped to section node %s" , field .getName (), value ));
303353 }
304354 }
305355 return value ;
306356 }
307357
358+ @ Nullable
359+ public Node <L > get (String path ) {
360+ Map <String , Node <L >> children = new HashMap <>();
361+
362+ for (Node <L > node : getNodes ()) {
363+ children .put (node .getName (), node );
364+ }
365+
366+ Node <L > current = this ;
367+ for (String key : path .split ("\\ ." )) {
368+ if (!(current instanceof SectionNode )) {
369+ // Path points nowhere.
370+ return null ;
371+ }
372+ current = current == this
373+ ? children .get (key )
374+ : ((SectionNode <L >) current ).get (key );
375+ }
376+ return current ;
377+ }
378+
308379 public SimpleNode <L > getSimple (String path ) {
309380 return (SimpleNode <L >) get (path );
310381 }
@@ -322,6 +393,10 @@ public <T extends Node<?>> List<T> getNodes(NodeTypeToken<T> type) {
322393 .collect (Collectors .toList ());
323394 }
324395
396+ public boolean has (String path ) {
397+ return get (path ) != null ;
398+ }
399+
325400 public NodeContext <Node <L >, L > getContext () {
326401 NodeContext <Node <L >, L > context = new NodeContext <>(this );
327402 getNodes ().forEach (node -> context .set (node .getName (), node ));
@@ -361,15 +436,20 @@ private boolean isPrimitive(Class<?> clazz) {
361436 return Primitives .isWrapperType (Primitives .wrap (clazz )) || String .class .equals (clazz );
362437 }
363438
364- @ SuppressWarnings ("rawtypes, unchecked " )
439+ @ SuppressWarnings ("rawtypes" )
365440 private <T extends NodeAdapter > T obtainAdapter (Object toBeSerialized , Class <T > adapterTypeClass ) {
441+ return obtainAdapter (toBeSerialized .getClass (), adapterTypeClass );
442+ }
443+
444+ @ SuppressWarnings ("rawtypes, unchecked" )
445+ private <T extends NodeAdapter > T obtainAdapter (Class <?> toBeSerialized , Class <T > adapterTypeClass ) {
366446 Validator .requireAnyType (adapterTypeClass , NodeSerializer .class , NodeDeserializer .class );
367447
368- // I allow users tto define their own serializers.
448+ // I allow users to define their own serializers.
369449 // @see NodeSerializer
370450 for (Class <?> aClass : adapters .keySet ()) {
371451 NodeAdapter <?, L > nodeAdapter = adapters .get (aClass );
372- if (adapterTypeClass .isAssignableFrom (nodeAdapter .getClass ()) && aClass .isAssignableFrom (toBeSerialized . getClass () )) {
452+ if (adapterTypeClass .isAssignableFrom (nodeAdapter .getClass ()) && aClass .isAssignableFrom (toBeSerialized )) {
373453 return (T ) nodeAdapter ;
374454 }
375455 }
@@ -394,7 +474,7 @@ public boolean isHighestLevel() {
394474 return parent == null ;
395475 }
396476
397- private void debug (String message ) {
477+ public void debug (String message ) {
398478 if (isContextDebug ()) {
399479 getContextLogAdapter ().log (Level .INFO , message );
400480 }
@@ -414,7 +494,7 @@ private LogAdapter getContextLogAdapter() {
414494 private boolean makeContextCheck (Predicate <SectionNode <L >> test ) {
415495 if (test .test (this ))
416496 return true ;
417- return parent != null && test . test ( parent );
497+ return parent != null && parent . makeContextCheck ( test );
418498 }
419499
420500 public static class DefaultNodeSerializer <L > implements NodeSerializer <Object , L > {
@@ -437,10 +517,6 @@ public void serialize(NodeContext context, Object from) {
437517 } else {
438518 name = field .getName ();
439519 }
440- /*getNodes().stream()
441- .filter(n -> n.getName().equals(name))
442- .findFirst()
443- .ifPresent(node -> node.set(value));*/
444520 context .set (name , value );
445521 } catch (IllegalAccessException e ) {
446522 e .printStackTrace ();
0 commit comments