This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new fc4d147aef Add fluent Config.entry(key) builder as an additive 
alternative to the 6-arg set() (TODO-350 B-config-2)
fc4d147aef is described below

commit fc4d147aef5cd18d3030bcd313863389a18ed6a2
Author: James Bognar <[email protected]>
AuthorDate: Thu Aug 13 15:57:18 2026 -0400

    Add fluent Config.entry(key) builder as an additive alternative to the 
6-arg set() (TODO-350 B-config-2)
    
    Introduces org.apache.juneau.config.EntryBuilder, reached via 
Config.entry(String key), replacing the 6-arg 
Config.set(key,value,serializer,modifiers,comment,preLines)'s three sentinel 
conventions (null=leave untouched, blank=clear comment, empty list=clear 
pre-lines) with explicit .comment/.preLines/.serializer/.modifiers setters plus 
.clearComment()/.clearPreLines() and terminal .set(value). "Leave untouched" is 
simply not calling a facet method; clearing is explicit. Delegates to t [...]
---
 .../main/java/org/apache/juneau/config/Config.java |  31 +++
 .../org/apache/juneau/config/EntryBuilder.java     | 221 +++++++++++++++++++++
 .../java/org/apache/juneau/config/Config_Test.java |  78 ++++++++
 3 files changed, 330 insertions(+)

diff --git 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
index 4ee7d8a113..afa1da2531 100644
--- 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
+++ 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
@@ -997,6 +997,37 @@ public class Config extends Context implements 
ConfigEventListener {
                return this;
        }
 
+       /**
+        * Returns a fluent builder for adding or updating the entry with the 
specified key.
+        *
+        * <p>
+        * This is an ergonomic, self-documenting alternative to the
+        * {@link #set(String, Object, Serializer, String, String, List)} 
overload.  Rather than overloading a single call
+        * with several "leave-unchanged versus clear" sentinel conventions, 
each facet is expressed explicitly:
+        * <ul>
+        *      <li><b>Not calling</b> a facet method leaves that facet 
untouched.
+        *      <li>An explicit setter ({@link EntryBuilder#comment(String)}, 
{@link EntryBuilder#preLines(List)},
+        *              {@link EntryBuilder#serializer(Serializer)}, {@link 
EntryBuilder#modifiers(String)}) sets that facet.
+        *      <li>An explicit clear ({@link EntryBuilder#clearComment()}, 
{@link EntryBuilder#clearPreLines()}) clears that facet.
+        * </ul>
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jv>config</jv>.entry(<js>"MySection/myKey"</js>)
+        *              .comment(<js>"My comment"</js>)
+        *              .preLines(List.<jsm>of</jsm>(<js>"# A pre-line"</js>))
+        *              .set(<js>"My value"</js>);
+        * </p>
+        *
+        * @param key
+        *      The key.
+        *      <br>Must not be <jk>null</jk>.
+        * @return A new fluent entry builder, never <jk>null</jk>.
+        */
+       public EntryBuilder entry(String key) {
+               return new EntryBuilder(this, key);
+       }
+
        /**
         * Adds or replaces an entry with the specified key with a POJO 
serialized to a string using the registered
         * serializer.
diff --git 
a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/EntryBuilder.java
 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/EntryBuilder.java
new file mode 100644
index 0000000000..66d75f46dd
--- /dev/null
+++ 
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/EntryBuilder.java
@@ -0,0 +1,221 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.config;
+
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
+
+import java.util.*;
+
+import org.apache.juneau.marshall.serializer.*;
+
+/**
+ * A fluent builder for adding or updating a single {@link Config} entry.
+ *
+ * <p>
+ * This is an ergonomic, self-documenting alternative to the multi-argument
+ * {@link Config#set(String, Object, Serializer, String, String, List)} 
overload.  That overload packs several distinct
+ * "leave-unchanged versus clear" sentinel conventions into one call (a 
<jk>null</jk> comment leaves the existing comment
+ * untouched while a blank comment clears it; a <jk>null</jk> pre-lines list 
leaves the existing pre-lines untouched while
+ * an empty list clears them).  This builder replaces those sentinels with 
explicit methods:
+ * <ul>
+ *     <li><b>Not calling</b> a facet method leaves that facet untouched (no 
sentinel needed).
+ *     <li>An explicit setter ({@link #comment(String)}, {@link 
#preLines(List)}, {@link #serializer(Serializer)},
+ *             {@link #modifiers(String)}) sets that facet.
+ *     <li>An explicit clear ({@link #clearComment()}, {@link 
#clearPreLines()}) intentionally clears that facet rather than
+ *             relying on a magic blank string or empty list.
+ * </ul>
+ *
+ * <p>
+ * The write is performed by the terminal {@link #set(Object)} or {@link 
#set()} method.  The result is identical to the
+ * equivalent {@link Config#set(String, Object, Serializer, String, String, 
List)} call.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     <jc>// Add an entry with a comment and pre-lines.</jc>
+ *     <jv>config</jv>.entry(<js>"MySection/myKey"</js>)
+ *             .comment(<js>"My comment"</js>)
+ *             .preLines(List.<jsm>of</jsm>(<js>"# A pre-line"</js>))
+ *             .set(<js>"My value"</js>);
+ *
+ *     <jc>// Update only the comment on an existing entry, leaving the value 
and pre-lines untouched.</jc>
+ *     <jv>config</jv>.entry(<js>"MySection/myKey"</js>)
+ *             .comment(<js>"Updated comment"</js>)
+ *             .set();
+ *
+ *     <jc>// Clear the comment on an existing entry.</jc>
+ *     <jv>config</jv>.entry(<js>"MySection/myKey"</js>)
+ *             .clearComment()
+ *             .set();
+ * </p>
+ *
+ * <h5 class='section'>Notes:</h5><ul>
+ *     <li class='note'>This builder is not thread safe and is intended to be 
used and discarded within a single statement.
+ * </ul>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/JuneauConfig";>juneau-config 
Basics</a>
+ * </ul>
+ */
+public class EntryBuilder {
+
+       private final Config config;
+       private final String key;
+
+       private Object value;
+       private Serializer serializer;
+       private String modifiers;
+       private String comment;
+       private List<String> preLines;
+
+       /**
+        * Constructor.
+        *
+        * @param config The config that the entry belongs to.
+        * @param key
+        *      The key of the entry to add or update.
+        *      <br>Must not be <jk>null</jk>.
+        */
+       protected EntryBuilder(Config config, String key) {
+               assertArgNotNull("key", key);
+               this.config = config;
+               this.key = key;
+       }
+
+       /**
+        * Specifies the value to store on the entry.
+        *
+        * <p>
+        * Equivalent to passing the value to the terminal {@link #set(Object)} 
method.
+        *
+        * @param value
+        *      The new value POJO.
+        *      <br>Serialized to a string using the registered (or {@link 
#serializer(Serializer) specified}) serializer.
+        * @return This object.
+        */
+       public EntryBuilder value(Object value) {
+               this.value = value;
+               return this;
+       }
+
+       /**
+        * Specifies the serializer to use to serialize the value.
+        *
+        * <p>
+        * If not called, the serializer registered on the config is used.
+        *
+        * @param value The serializer to use for serializing the value.
+        * @return This object.
+        */
+       public EntryBuilder serializer(Serializer value) {
+               this.serializer = value;
+               return this;
+       }
+
+       /**
+        * Specifies the modifiers to apply to the value (e.g. <js>"*"</js> for 
encoded values).
+        *
+        * <p>
+        * If not called, the modifiers on any existing entry are left 
untouched.
+        *
+        * @param value The modifiers to apply to the value.
+        * @return This object.
+        */
+       public EntryBuilder modifiers(String value) {
+               this.modifiers = value;
+               return this;
+       }
+
+       /**
+        * Specifies the same-line comment to add to the entry.
+        *
+        * <p>
+        * If not called, the comment on any existing entry is left untouched.
+        * <br>Use {@link #clearComment()} to explicitly remove an existing 
comment.
+        *
+        * @param value The same-line comment to add to the entry.  Must not be 
<jk>null</jk> (use {@link #clearComment()} instead).
+        * @return This object.
+        */
+       public EntryBuilder comment(String value) {
+               assertArgNotNull("value", value);
+               this.comment = value;
+               return this;
+       }
+
+       /**
+        * Explicitly clears the same-line comment on the entry.
+        *
+        * @return This object.
+        */
+       public EntryBuilder clearComment() {
+               this.comment = "";
+               return this;
+       }
+
+       /**
+        * Specifies the comment or blank lines to add before the entry.
+        *
+        * <p>
+        * If not called, the pre-lines on any existing entry are left 
untouched.
+        * <br>Use {@link #clearPreLines()} to explicitly remove existing 
pre-lines.
+        *
+        * @param value The comment or blank lines to add before the entry.  
Must not be <jk>null</jk> (use {@link #clearPreLines()} instead).
+        * @return This object.
+        */
+       public EntryBuilder preLines(List<String> value) {
+               assertArgNotNull("value", value);
+               this.preLines = value;
+               return this;
+       }
+
+       /**
+        * Explicitly clears the pre-lines on the entry.
+        *
+        * @return This object.
+        */
+       public EntryBuilder clearPreLines() {
+               this.preLines = Collections.emptyList();
+               return this;
+       }
+
+       /**
+        * Adds or updates the entry using the value specified via {@link 
#value(Object)}.
+        *
+        * @return The config that this builder belongs to.
+        * @throws SerializeException
+        *      If serializer could not serialize the value or if a serializer 
is not registered with this config file.
+        * @throws UnsupportedOperationException If configuration is read only.
+        */
+       public Config set() throws SerializeException {
+               return config.set(key, value, serializer, modifiers, comment, 
preLines);
+       }
+
+       /**
+        * Adds or updates the entry using the specified value.
+        *
+        * @param value
+        *      The new value POJO.
+        *      <br>Serialized to a string using the registered (or {@link 
#serializer(Serializer) specified}) serializer.
+        * @return The config that this builder belongs to.
+        * @throws SerializeException
+        *      If serializer could not serialize the value or if a serializer 
is not registered with this config file.
+        * @throws UnsupportedOperationException If configuration is read only.
+        */
+       public Config set(Object value) throws SerializeException {
+               this.value = value;
+               return set();
+       }
+}
diff --git 
a/juneau-core/juneau-config/src/test/java/org/apache/juneau/config/Config_Test.java
 
b/juneau-core/juneau-config/src/test/java/org/apache/juneau/config/Config_Test.java
index 2838b9b6b6..36ef157760 100644
--- 
a/juneau-core/juneau-config/src/test/java/org/apache/juneau/config/Config_Test.java
+++ 
b/juneau-core/juneau-config/src/test/java/org/apache/juneau/config/Config_Test.java
@@ -208,6 +208,84 @@ class Config_Test extends TestBase {
                assertEquals("(foo=bar)", c.get("T/c1").get());
        }
 
+       
//====================================================================================================
+       //      public EntryBuilder entry(String key) - fluent alternative to 
the 6-arg set(...)
+       
//====================================================================================================
+       @Test void b05b_entryBuilder_parityWith6ArgSet() throws Exception {
+               var c = init("a1=1", "[S]", "b1=1");
+
+               var b = new ABean().init();
+
+               // Equivalent 6-arg call.
+               c.set("a1", b, UonSerializer.DEFAULT, "*", "comment", l("#c1", 
"#c2"));
+               // Fluent equivalent - each facet expressed explicitly.
+               
c.entry("a2").serializer(UonSerializer.DEFAULT).modifiers("*").comment("comment").preLines(l("#c1",
 "#c2")).set(b);
+
+               // Both paths produce an identical stored result.
+               var e1 = c.get("a1");
+               var e2 = c.get("a2");
+               assertEquals(e1.getValue(), e2.getValue());
+               assertEquals(e1.getModifiers(), e2.getModifiers());
+               assertEquals(e1.getComment(), e2.getComment());
+               assertEquals(e1.getPreLines(), e2.getPreLines());
+               assertEquals("(foo=bar)", e2.get());
+       }
+
+       @Test void b05c_entryBuilder_leaveUntouchedVsClear() throws Exception {
+               var c = init("a0=0");
+
+               // Seed an entry with a comment and pre-lines.
+               c.entry("a1").comment("orig").preLines(l("#p1")).set("v1");
+               assertEquals("v1", c.get("a1").getValue());
+               assertEquals("orig", c.get("a1").getComment());
+               assertEquals(l("#p1"), c.get("a1").getPreLines());
+
+               // Leave untouched: facet methods not called, so comment + 
pre-lines are preserved.
+               c.entry("a1").set("v2");
+               assertEquals("v2", c.get("a1").getValue());
+               assertEquals("orig", c.get("a1").getComment());
+               assertEquals(l("#p1"), c.get("a1").getPreLines());
+
+               // Explicit clear: comment + pre-lines are removed (distinct 
from leave-untouched).
+               c.entry("a1").clearComment().clearPreLines().set("v3");
+               assertEquals("v3", c.get("a1").getValue());
+               assertEquals("", c.get("a1").getComment());
+               assertTrue(c.get("a1").getPreLines().isEmpty());
+       }
+
+       @Test void b05d_entryBuilder_roundTrip() throws Exception {
+               var c = init("a1=1", "[S]", "b1=1");
+
+               var b = new ABean().init();
+               
c.entry("a1").serializer(UonSerializer.DEFAULT).modifiers("*").comment("comment").preLines(l("#c1",
 "#c2")).set(b);
+               
c.entry("S/b1").serializer(UonSerializer.DEFAULT).comment("comment2").set(b);
+
+               var before = pipedLines(c);
+               c.commit();
+               assertEquals(before, pipedLines(c));
+               c = cb.build();
+               assertEquals(before, pipedLines(c));
+
+               assertEquals("(foo=bar)", c.get("a1").get());
+               assertEquals("comment", c.get("a1").getComment());
+               assertEquals(l("#c1", "#c2"), c.get("a1").getPreLines());
+               assertEquals("(foo=bar)", c.get("S/b1").get());
+               assertEquals("comment2", c.get("S/b1").getComment());
+       }
+
+       @Test void b05e_entryBuilder_valueFormsAndValidation() throws Exception 
{
+               var c = init("a0=0");
+
+               // The .value(x).set() and .set(x) forms are equivalent.
+               c.entry("a1").value("v").set();
+               c.entry("a2").set("v");
+               assertEquals(c.get("a1").getValue(), c.get("a2").getValue());
+
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'key' cannot be null.", ()->c.entry(null));
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'value' cannot be null.", ()->c.entry("a3").comment(null));
+               assertThrowsWithMessage(IllegalArgumentException.class, 
"Argument 'value' cannot be null.", ()->c.entry("a3").preLines(null));
+       }
+
        
//====================================================================================================
        //      public Config remove(String key)
        
//====================================================================================================

Reply via email to