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)
//====================================================================================================