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 77c8eeef91 juneau-marshall improvements
77c8eeef91 is described below

commit 77c8eeef91e32b53adc6b7c6ca337039d1f21ebb
Author: James Bognar <[email protected]>
AuthorDate: Tue Dec 16 08:48:01 2025 -0500

    juneau-marshall improvements
---
 .../apache/juneau/commons/settings/Settings.java   | 106 ++++++++++
 .../java/org/apache/juneau/AnnotationWorkList.java |  23 +--
 .../org/apache/juneau/BasicAssertionError.java     |  22 +-
 .../java/org/apache/juneau/BasicException.java     |  85 --------
 .../org/apache/juneau/BasicRuntimeException.java   |  75 +------
 .../main/java/org/apache/juneau/BeanContext.java   |  69 ++++---
 .../org/apache/juneau/BeanRecursionException.java  |   6 +-
 .../juneau/http/response/BasicHttpException.java   |   7 -
 .../juneau/commons/settings/Settings_Test.java     | 229 +++++++++++++++++++++
 9 files changed, 389 insertions(+), 233 deletions(-)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
index 9565cb550d..5fb3891c0c 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/settings/Settings.java
@@ -138,6 +138,13 @@ public class Settings {
        private static final String MSG_globalDisabled = "Global settings not 
enabled";
        private static final String MSG_localDisabled = "Local settings not 
enabled";
 
+       private static final Map<Class<?>,Function<String,?>> 
DEFAULT_TYPE_FUNCTIONS = new IdentityHashMap<>();
+
+       static {
+               DEFAULT_TYPE_FUNCTIONS.put(Boolean.class, Boolean::valueOf);
+               DEFAULT_TYPE_FUNCTIONS.put(Charset.class, Charset::forName);
+       }
+
        /**
         * Returns properties for this Settings object itself.
         * Note that these are initialized at startup and not changeable 
through System.setProperty().
@@ -183,6 +190,7 @@ public class Settings {
                private Supplier<SettingStore> globalStoreSupplier = () -> new 
MapStore();
                private Supplier<SettingStore> localStoreSupplier = () -> new 
MapStore();
                private final List<SettingSource> sources = new ArrayList<>();
+               private final Map<Class<?>,Function<String,?>> 
customTypeFunctions = new IdentityHashMap<>();
 
                /**
                 * Sets the supplier for the global store.
@@ -244,6 +252,39 @@ public class Settings {
                        return addSource((SettingSource)source);
                }
 
+               /**
+                * Registers a custom type conversion function for the 
specified type.
+                *
+                * <p>
+                * This allows you to add support for converting string values 
to custom types when using
+                * {@link Settings#get(String, Object)}. The function will be 
used to convert string values
+                * to the specified type.
+                *
+                * <h5 class='section'>Example:</h5>
+                * <p class='bjava'>
+                *      <jc>// Register a custom converter for a custom 
type</jc>
+                *      Settings <jv>custom</jv> = Settings.<jsf>create</jsf>()
+                *              .addTypeFunction(Integer.<jk>class</jk>, 
Integer::valueOf)
+                *              .addTypeFunction(MyCustomType.<jk>class</jk>, 
MyCustomType::fromString)
+                *              .build();
+                *
+                *      <jc>// Now you can use get() with these types</jc>
+                *      Integer <jv>intValue</jv> = 
<jv>custom</jv>.get(<js>"my.int.property"</js>, 0);
+                *      MyCustomType <jv>customValue</jv> = 
<jv>custom</jv>.get(<js>"my.custom.property"</js>, MyCustomType.DEFAULT);
+                * </p>
+                *
+                * @param <T> The type to register a converter for.
+                * @param type The class type to register a converter for. Must 
not be <c>null</c>.
+                * @param function The function that converts a string to the 
specified type. Must not be <c>null</c>.
+                * @return This builder for method chaining.
+                */
+               public <T> Builder addTypeFunction(Class<T> type, 
Function<String,T> function) {
+                       assertArgNotNull("type", type);
+                       assertArgNotNull("function", function);
+                       customTypeFunctions.put(type, function);
+                       return this;
+               }
+
                /**
                 * Builds a Settings instance from this builder.
                 *
@@ -271,6 +312,7 @@ public class Settings {
        private final ResettableSupplier<SettingStore> globalStore;
        private final ThreadLocal<SettingStore> localStore;
        private final List<SettingSource> sources;
+       private final Map<Class<?>,Function<String,?>> customTypeFunctions;
 
        /**
         * Constructor.
@@ -279,6 +321,7 @@ public class Settings {
                this.globalStore = 
memoizeResettable(builder.globalStoreSupplier);
                this.localStore = 
ThreadLocal.withInitial(builder.localStoreSupplier);
                this.sources = new CopyOnWriteArrayList<>(builder.sources);
+               this.customTypeFunctions = new 
IdentityHashMap<>(builder.customTypeFunctions);
        }
 
        /**
@@ -298,6 +341,8 @@ public class Settings {
         * @return The property value, or {@link Optional#empty()} if not found.
         */
        public Optional<String> get(String name) {
+               assertArgNotNull("name", name);
+
                // 1. Check thread-local override
                var v = localStore.get().get(name);
                if (v != null)
@@ -319,6 +364,38 @@ public class Settings {
                return opte();
        }
 
+       /**
+        * Looks up a system property, returning a default value if not found.
+        *
+        * <p>
+        * This method searches for a value using the same lookup order as 
{@link #get(String)}.
+        * If a value is found, it is converted to the type of the default 
value using {@link #toType(String, Object)}.
+        * Supported types include {@link Boolean}, {@link Charset}, and other 
common types.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// System property: -Dmy.property=true</jc>
+        *      Boolean <jv>flag</jv> = get(<js>"my.property"</js>, 
<jk>false</jk>);  <jc>// true</jc>
+        *
+        *      <jc>// Environment variable: MY_PROPERTY=UTF-8</jc>
+        *      Charset <jv>charset</jv> = get(<js>"my.property"</js>, 
Charset.defaultCharset());  <jc>// UTF-8</jc>
+        *
+        *      <jc>// Not found, returns default</jc>
+        *      String <jv>value</jv> = get(<js>"nonexistent"</js>, 
<js>"default"</js>);  <jc>// "default"</jc>
+        * </p>
+        *
+        * @param <T> The type to convert the value to.
+        * @param name The property name.
+        * @param def The default value to return if not found.
+        * @return The found value (converted to type T), or the default value 
if not found.
+        * @see #get(String)
+        * @see #toType(String, Object)
+        */
+       public <T> T get(String name, T def) {
+               assertArgNotNull("def", def);
+               return get(name).map(x -> toType(x, def)).orElse(def);
+       }
+
        /**
         * Returns the value of the specified system property as an Integer.
         *
@@ -571,5 +648,34 @@ public class Settings {
                
globalStore.orElseThrow(()->illegalState(MSG_globalDisabled)).clear();
                return this;
        }
+
+       /**
+        * Converts a string to the specified type using registered conversion 
functions.
+        *
+        * @param <T> The target type.
+        * @param s The string to convert. Must not be <jk>null</jk>.
+        * @param def The default value (used to determine the target type). 
Must not be <jk>null</jk>.
+        * @return The converted value.
+        * @throws RuntimeException If the type is not supported for conversion.
+        */
+       @SuppressWarnings({ "unchecked", "rawtypes" })
+       private <T> T toType(String s, T def) {
+               var c = (Class<T>)def.getClass();
+               var f = (Function<String,T>)customTypeFunctions.get(c);
+               if (f == null) {
+                       if (c == String.class)
+                               return (T)s;
+                       if (c.isEnum())
+                               return (T)Enum.valueOf((Class<? extends 
Enum>)c, s);
+                       f = (Function<String,T>)DEFAULT_TYPE_FUNCTIONS.get(c);
+                       if (f == null)
+                               f = 
customTypeFunctions.entrySet().stream().filter(x -> 
x.getKey().isAssignableFrom(c)).map(x -> 
(Function<String,T>)x.getValue()).findFirst().orElse(null);
+                       if (f == null)
+                               f = 
DEFAULT_TYPE_FUNCTIONS.entrySet().stream().filter(x -> 
x.getKey().isAssignableFrom(c)).map(x -> 
(Function<String,T>)x.getValue()).findFirst().orElse(null);
+               }
+               if (f == null)
+                       throw rex("Invalid env type: {0}", c);
+               return f.apply(s);
+       }
 }
 
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/AnnotationWorkList.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/AnnotationWorkList.java
index 008a3a0424..b75eb53b75 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/AnnotationWorkList.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/AnnotationWorkList.java
@@ -31,7 +31,6 @@ import org.apache.juneau.svl.*;
 /**
  * A list of {@link AnnotationWork} objects.
  *
- *
  * @serial exclude
  */
 public class AnnotationWorkList extends ArrayList<AnnotationWork> {
@@ -113,21 +112,13 @@ public class AnnotationWorkList extends 
ArrayList<AnnotationWork> {
         */
        @SuppressWarnings("unchecked")
        private void applyAnnotation(AnnotationInfo<?> ai) {
-               try {
-                       var a = ai.inner();
-                       var cpa = 
assertNotNull(a.annotationType().getAnnotation(ContextApply.class), "Annotation 
found without @ContextApply: %s", cn(ai.annotationType()));
-                       Constructor<? extends AnnotationApplier<?,?>>[] 
applyConstructors;
-
-                       applyConstructors = new Constructor[cpa.value().length];
-                       for (var i = 0; i < cpa.value().length; i++)
-                               applyConstructors[i] = (Constructor<? extends 
AnnotationApplier<?,?>>)cpa.value()[i].getConstructor(VarResolverSession.class);
-
-                       for (var applyConstructor : applyConstructors) {
-                               AnnotationApplier<Annotation,Object> applier = 
(AnnotationApplier<Annotation,Object>)applyConstructor.newInstance(vrs);
+               var a = ai.inner();
+               var cpa = 
assertNotNull(a.annotationType().getAnnotation(ContextApply.class), "Annotation 
found without @ContextApply: %s", cn(ai.annotationType()));
+               Arrays.stream(cpa.value())
+                       .map(x -> safe(() -> (Constructor<? extends 
AnnotationApplier<?,?>>)x.getConstructor(VarResolverSession.class)))
+                       .forEach(applyConstructor -> {
+                               var applier = safe(() -> 
(AnnotationApplier<Annotation,Object>)applyConstructor.newInstance(vrs));
                                add(ai, applier);
-                       }
-               } catch (InstantiationException | IllegalAccessException | 
IllegalArgumentException | InvocationTargetException | NoSuchMethodException | 
SecurityException e) {
-                       throw new ExecutableException(e);
-               }
+                       });
        }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicAssertionError.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicAssertionError.java
index 9d80335662..71ebe8e137 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicAssertionError.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicAssertionError.java
@@ -22,30 +22,11 @@ import java.text.*;
 
 /**
  * An extension of {@link AssertionError} with helper constructors for 
messages with message-style arguments.
- *
- *
- * @serial exclude
  */
 public class BasicAssertionError extends AssertionError {
 
        private static final long serialVersionUID = 1L;
 
-       /**
-        * Finds the message.
-        *
-        * @param cause The cause.
-        * @param msg The message.
-        * @param def The default value if both above are <jk>null</jk>.
-        * @return The resolved message.
-        */
-       protected static final String getMessage(Throwable cause, String msg, 
String def) {
-               if (nn(msg))
-                       return msg;
-               if (nn(cause))
-                       return cause.getMessage();
-               return def;
-       }
-
        /**
         * Constructor.
         *
@@ -64,7 +45,6 @@ public class BasicAssertionError extends AssertionError {
         * @param args Optional {@link MessageFormat}-style arguments.
         */
        public BasicAssertionError(Throwable cause, String message, 
Object...args) {
-               this(getMessage(cause, message, null), args);
-               initCause(cause);
+               super(f(message, args), cause);
        }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicException.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicException.java
deleted file mode 100644
index 3f6f01a637..0000000000
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicException.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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;
-
-import static org.apache.juneau.commons.utils.ThrowableUtils.*;
-import static org.apache.juneau.commons.utils.Utils.*;
-
-import java.text.*;
-
-/**
- * Subclass of non-runtime exceptions that take in a message and zero or more 
arguments.
- *
- *
- * @serial exclude
- */
-public abstract class BasicException extends Exception {
-
-       private static final long serialVersionUID = 1L;
-
-       /**
-        * Constructor.
-        *
-        * @param message The {@link MessageFormat}-style message.
-        * @param args Optional {@link MessageFormat}-style arguments.
-        */
-       public BasicException(String message, Object...args) {
-               super(f(message, args));
-       }
-
-       /**
-        * Constructor.
-        *
-        * @param causedBy The cause of this exception.
-        */
-       public BasicException(Throwable causedBy) {
-               this(causedBy, lm(causedBy));
-       }
-
-       /**
-        * Constructor.
-        *
-        * @param causedBy The cause of this exception.
-        * @param message The {@link MessageFormat}-style message.
-        * @param args Optional {@link MessageFormat}-style arguments.
-        */
-       public BasicException(Throwable causedBy, String message, 
Object...args) {
-               this(message, args);
-               initCause(causedBy);
-       }
-
-       /**
-        * Same as {@link #getCause()} but searches the throwable chain for an 
exception of the specified type.
-        *
-        * @param c The throwable type to search for.
-        * @param <T> The throwable type to search for.
-        * @return The exception, or <jk>null</jk> if not found.
-        */
-       public <T extends Throwable> T getCause(Class<T> c) {
-               return getThrowableCause(c, this);
-       }
-
-       /**
-        * Returns the caused-by exception if there is one.
-        *
-        * @return The caused-by exception if there is one, or this exception 
if there isn't.
-        */
-       public Throwable unwrap() {
-               Throwable t = getCause();
-               return t == null ? this : t;
-       }
-}
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicRuntimeException.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicRuntimeException.java
index 794e22f03d..22788e6f0b 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicRuntimeException.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicRuntimeException.java
@@ -16,7 +16,6 @@
  */
 package org.apache.juneau;
 
-import static org.apache.juneau.commons.utils.ThrowableUtils.*;
 import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.text.*;
@@ -62,46 +61,11 @@ public class BasicRuntimeException extends RuntimeException 
{
                super(f(message, args), cause);
        }
 
-       @Override /* Overridden from Throwable */
-       public synchronized Throwable fillInStackTrace() {
-               assertModifiable();
-               return super.fillInStackTrace();
-       }
-
-       /**
-        * Same as {@link #getCause()} but searches the throwable chain for an 
exception of the specified type.
-        *
-        * @param c The throwable type to search for.
-        * @param <T> The throwable type to search for.
-        * @return The exception, or <jk>null</jk> if not found.
-        */
-       public <T extends Throwable> T getCause(Class<T> c) {
-               return getThrowableCause(c, this);
-       }
-
        @Override /* Overridden from Throwable */
        public String getMessage() {
-               if (nn(message))
-                       return message;
-               String m = super.getMessage();
-               if (m == null && nn(getCause()))
-                       m = getCause().getMessage();
-               return m;
+               return nn(message) ? message : super.getMessage();
        }
 
-       @Override /* Overridden from Throwable */
-       public synchronized Throwable initCause(Throwable cause) {
-               assertModifiable();
-               return super.initCause(cause);
-       }
-
-       /**
-        * Returns <jk>true</jk> if this bean is unmodifiable.
-        *
-        * @return <jk>true</jk> if this bean is unmodifiable.
-        */
-       public boolean isUnmodifiable() { return unmodifiable; }
-
        /**
         * Sets the detail message on this exception.
         *
@@ -110,44 +74,7 @@ public class BasicRuntimeException extends RuntimeException 
{
         * @return This object.
         */
        public BasicRuntimeException setMessage(String message, Object...args) {
-               assertModifiable();
                this.message = f(message, args);
                return this;
        }
-
-       @Override /* Overridden from Throwable */
-       public void setStackTrace(StackTraceElement[] stackTrace) {
-               assertModifiable();
-               super.setStackTrace(stackTrace);
-       }
-
-       /**
-        * Returns the caused-by exception if there is one.
-        *
-        * @return The caused-by exception if there is one, or this exception 
if there isn't.
-        */
-       public Throwable unwrap() {
-               Throwable t = getCause();
-               return t == null ? this : t;
-       }
-
-       /**
-        * Throws an {@link UnsupportedOperationException} if the unmodifiable 
flag is set on this bean.
-        */
-       protected final void assertModifiable() {
-               if (unmodifiable)
-                       throw unsupportedOp("Bean is read-only");
-       }
-
-       /**
-        * Specifies whether this bean should be unmodifiable.
-        * <p>
-        * When enabled, attempting to set any properties on this bean will 
cause an {@link UnsupportedOperationException}.
-        *
-        * @return This object.
-        */
-       protected BasicRuntimeException setUnmodifiable() {
-               unmodifiable = true;
-               return this;
-       }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
index 2574d7f160..2e022728d6 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
@@ -22,6 +22,7 @@ import static org.apache.juneau.commons.utils.ClassUtils.*;
 import static org.apache.juneau.commons.utils.CollectionUtils.*;
 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
 import static org.apache.juneau.commons.utils.Utils.*;
+import static java.util.Comparator.*;
 
 import java.beans.*;
 import java.io.*;
@@ -37,6 +38,7 @@ import org.apache.juneau.commons.collections.FluentMap;
 import org.apache.juneau.commons.function.*;
 import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.commons.reflect.Visibility;
+import org.apache.juneau.commons.settings.*;
 import org.apache.juneau.cp.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.marshaller.*;
@@ -171,39 +173,50 @@ public class BeanContext extends Context {
        public static class Builder extends Context.Builder {
 
                private static final Cache<HashKey,BeanContext> CACHE = 
Cache.of(HashKey.class, BeanContext.class).build();
+               private static final Settings SETTINGS = Settings.get();
 
                private static Set<Class<?>> classSet() {
-                       return new 
TreeSet<>(Comparator.comparing(Class::getName));
+                       return new TreeSet<>(comparing(Class::getName));
                }
 
-               private static Set<Class<?>> classSet(Collection<Class<?>> 
copy) {
-                       return classSet(copy, false);
-               }
-
-               private static Set<Class<?>> classSet(Collection<Class<?>> 
copy, boolean nullIfEmpty) {
-                       if (copy == null || (nullIfEmpty && copy.isEmpty()))
+               private static Set<Class<?>> toClassSet(Collection<Class<?>> 
copy) {
+                       if (copy == null)
                                return null;
-                       Set<Class<?>> x = classSet();
+                       var x = classSet();
                        x.addAll(copy);
                        return x;
                }
 
-               Visibility beanClassVisibility, beanConstructorVisibility, 
beanMethodVisibility, beanFieldVisibility;
-               boolean disableBeansRequireSomeProperties, 
beanMapPutReturnsOldValue, beansRequireDefaultConstructor, 
beansRequireSerializable, beansRequireSettersForGetters, 
disableIgnoreTransientFields,
-                       disableIgnoreUnknownNullBeanProperties, 
disableIgnoreMissingSetters, disableInterfaceProxies, findFluentSetters, 
ignoreInvocationExceptionsOnGetters, ignoreInvocationExceptionsOnSetters,
-                       ignoreUnknownBeanProperties, ignoreUnknownEnumValues, 
sortProperties, useEnumNames, useJavaBeanIntrospector;
-               String typePropertyName;
-               MediaType mediaType;
-               Locale locale;
-               TimeZone timeZone;
-               Class<? extends PropertyNamer> propertyNamer;
-               List<Class<?>> beanDictionary;
-
-               List<Object> swaps;
-
-               Set<Class<?>> notBeanClasses;
-
-               Set<String> notBeanPackages;
+               private Visibility beanClassVisibility;
+               private Visibility beanConstructorVisibility;
+               private Visibility beanMethodVisibility;
+               private Visibility beanFieldVisibility;
+               private boolean disableBeansRequireSomeProperties;
+               private boolean beanMapPutReturnsOldValue;
+               private boolean beansRequireDefaultConstructor;
+               private boolean beansRequireSerializable;
+               private boolean beansRequireSettersForGetters;
+               private boolean disableIgnoreTransientFields;
+               private boolean disableIgnoreUnknownNullBeanProperties;
+               private boolean disableIgnoreMissingSetters;
+               private boolean disableInterfaceProxies;
+               private boolean findFluentSetters;
+               private boolean ignoreInvocationExceptionsOnGetters;
+               private boolean ignoreInvocationExceptionsOnSetters;
+               private boolean ignoreUnknownBeanProperties;
+               private boolean ignoreUnknownEnumValues;
+               private boolean sortProperties;
+               private boolean useEnumNames;
+               private boolean useJavaBeanIntrospector;
+               private String typePropertyName;
+               private MediaType mediaType;
+               private Locale locale;
+               private TimeZone timeZone;
+               private Class<? extends PropertyNamer> propertyNamer;
+               private List<Class<?>> beanDictionary;
+               private List<Object> swaps;
+               private Set<Class<?>> notBeanClasses;
+               private Set<String> notBeanPackages;
 
                /**
                 * Constructor.
@@ -254,10 +267,10 @@ public class BeanContext extends Context {
                        beanConstructorVisibility = 
copyFrom.beanConstructorVisibility;
                        beanMethodVisibility = copyFrom.beanMethodVisibility;
                        beanFieldVisibility = copyFrom.beanFieldVisibility;
-                       beanDictionary = toList(copyFrom.beanDictionary, true);
+                       beanDictionary = toList(copyFrom.beanDictionary, false);
                        swaps = toList(copyFrom.swaps, true);
-                       notBeanClasses = classSet(copyFrom.notBeanClasses, 
true);
-                       notBeanPackages = toSortedSet(copyFrom.notBeanPackages, 
true);
+                       notBeanClasses = toClassSet(copyFrom.notBeanClasses);
+                       notBeanPackages = toSortedSet(copyFrom.notBeanPackages, 
false);
                        disableBeansRequireSomeProperties = ! 
copyFrom.beansRequireSomeProperties;
                        beanMapPutReturnsOldValue = 
copyFrom.beanMapPutReturnsOldValue;
                        beansRequireDefaultConstructor = 
copyFrom.beansRequireDefaultConstructor;
@@ -295,7 +308,7 @@ public class BeanContext extends Context {
                        beanFieldVisibility = copyFrom.beanFieldVisibility;
                        beanDictionary = copyOf(copyFrom.beanDictionary);
                        swaps = copyOf(copyFrom.swaps);
-                       notBeanClasses = classSet(copyFrom.notBeanClasses);
+                       notBeanClasses = toClassSet(copyFrom.notBeanClasses);
                        notBeanPackages = toSortedSet(copyFrom.notBeanPackages);
                        disableBeansRequireSomeProperties = 
copyFrom.disableBeansRequireSomeProperties;
                        beanMapPutReturnsOldValue = 
copyFrom.beanMapPutReturnsOldValue;
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRecursionException.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRecursionException.java
index 8a2416ddd5..6bd3c23cf1 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRecursionException.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanRecursionException.java
@@ -16,6 +16,8 @@
  */
 package org.apache.juneau;
 
+import static org.apache.juneau.commons.utils.Utils.*;
+
 import java.text.*;
 
 /**
@@ -24,7 +26,7 @@ import java.text.*;
  *
  * @serial exclude
  */
-public class BeanRecursionException extends BasicException {
+public class BeanRecursionException extends Exception {
 
        private static final long serialVersionUID = 1L;
 
@@ -35,6 +37,6 @@ public class BeanRecursionException extends BasicException {
         * @param args Optional {@link MessageFormat}-style arguments.
         */
        public BeanRecursionException(String message, Object...args) {
-               super(message, args);
+               super(f(message, args));
        }
 }
\ No newline at end of file
diff --git 
a/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/response/BasicHttpException.java
 
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/response/BasicHttpException.java
index cc6841f045..029439eb96 100644
--- 
a/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/response/BasicHttpException.java
+++ 
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/response/BasicHttpException.java
@@ -216,7 +216,6 @@ public class BasicHttpException extends 
BasicRuntimeException implements HttpRes
         * @return The underlying builder for the headers.
         */
        public HeaderList getHeaders() {
-               assertModifiable();
                return headers;
        }
 
@@ -314,7 +313,6 @@ public class BasicHttpException extends 
BasicRuntimeException implements HttpRes
         * @return This object.
         */
        public BasicHttpException setContent(HttpEntity value) {
-               assertModifiable();
                content = value;
                return this;
        }
@@ -332,7 +330,6 @@ public class BasicHttpException extends 
BasicRuntimeException implements HttpRes
 
        @Override /* Overridden from HttpMessage */
        public void setEntity(HttpEntity entity) {
-               assertModifiable();
                this.content = entity;
        }
 
@@ -370,7 +367,6 @@ public class BasicHttpException extends 
BasicRuntimeException implements HttpRes
         * @return This object.
         */
        public BasicHttpException setHeaders(HeaderList value) {
-               assertModifiable();
                headers = value.copy();
                return this;
        }
@@ -501,7 +497,6 @@ public class BasicHttpException extends 
BasicRuntimeException implements HttpRes
         * @return This object.
         */
        public BasicHttpException setStatusLine(BasicStatusLine value) {
-               assertModifiable();
                statusLine = value.copy();
                return this;
        }
@@ -528,9 +523,7 @@ public class BasicHttpException extends 
BasicRuntimeException implements HttpRes
         *
         * @return This object.
         */
-       @Override
        public BasicHttpException setUnmodifiable() {
-               super.setUnmodifiable();
                statusLine.setUnmodifiable();
                return this;
        }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
index 2172e49477..b471c88e4f 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/settings/Settings_Test.java
@@ -870,5 +870,234 @@ class Settings_Test extends TestBase {
                var result = store.get(TEST_PROP);
                assertNull(result);
        }
+
+       
//====================================================================================================
+       // get(String, T) - Type conversion with default value
+       
//====================================================================================================
+       @Test
+       void u01_get_withDefaultString_found() {
+               System.setProperty(TEST_PROP, "found-value");
+               var result = Settings.get().get(TEST_PROP, "default-value");
+               assertEquals("found-value", result);
+       }
+
+       @Test
+       void u02_get_withDefaultString_notFound() {
+               var result = Settings.get().get("nonexistent.property", 
"default-value");
+               assertEquals("default-value", result);
+       }
+
+       @Test
+       void u03_get_withDefaultBoolean_found() {
+               System.setProperty(TEST_PROP, "true");
+               var result = Settings.get().get(TEST_PROP, false);
+               assertTrue(result);
+       }
+
+       @Test
+       void u04_get_withDefaultBoolean_notFound() {
+               var result = Settings.get().get("nonexistent.property", true);
+               assertTrue(result);
+       }
+
+       @Test
+       void u05_get_withDefaultBoolean_falseValue() {
+               System.setProperty(TEST_PROP, "false");
+               var result = Settings.get().get(TEST_PROP, true);
+               assertFalse(result);
+       }
+
+       @Test
+       void u06_get_withDefaultCharset_found() {
+               System.setProperty(TEST_PROP, "ISO-8859-1");
+               // Use Charset.forName to get a Charset instance (not a 
concrete implementation)
+               var defaultCharset = Charset.forName("UTF-8");
+               var result = Settings.get().get(TEST_PROP, defaultCharset);
+               assertEquals(Charset.forName("ISO-8859-1"), result);
+       }
+
+       @Test
+       void u07_get_withDefaultCharset_notFound() {
+               // Use Charset.forName to get a Charset instance (not a 
concrete implementation)
+               var defaultCharset = Charset.forName("UTF-8");
+               var result = Settings.get().get("nonexistent.property", 
defaultCharset);
+               assertEquals(defaultCharset, result);
+       }
+
+       @Test
+       void u08_get_withDefaultEnum_found() {
+               enum TestEnum { VALUE1, VALUE2, VALUE3 }
+               System.setProperty(TEST_PROP, "VALUE2");
+               var result = Settings.get().get(TEST_PROP, TestEnum.VALUE1);
+               assertEquals(TestEnum.VALUE2, result);
+       }
+
+       @Test
+       void u09_get_withDefaultEnum_notFound() {
+               enum TestEnum { VALUE1, VALUE2, VALUE3 }
+               var result = Settings.get().get("nonexistent.property", 
TestEnum.VALUE1);
+               assertEquals(TestEnum.VALUE1, result);
+       }
+
+       @Test
+       void u10_get_withDefaultString_nullDefault() {
+               // Null defaults are not allowed
+               System.setProperty(TEST_PROP, "found-value");
+               assertThrows(IllegalArgumentException.class, () -> {
+                       Settings.get().get(TEST_PROP, (String)null);
+               });
+       }
+
+       @Test
+       void u11_get_withDefaultString_nullProperty() {
+               Settings.get().setLocal(TEST_PROP, null);
+               var result = Settings.get().get(TEST_PROP, "default-value");
+               // When property is set to null, get() returns 
Optional.empty(), so get(String, T) returns default
+               assertEquals("default-value", result);
+       }
+
+       
//====================================================================================================
+       // addTypeFunction() - Custom type conversion
+       
//====================================================================================================
+       @Test
+       void v01_addTypeFunction_customType() {
+               // Register a custom type converter for Integer
+               var settings = Settings.create()
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .addTypeFunction(Integer.class, Integer::valueOf)
+                       .build();
+
+               System.setProperty(TEST_PROP, "123");
+               var result = settings.get(TEST_PROP, 0);
+               assertEquals(123, result.intValue());
+       }
+
+       @Test
+       void v02_addTypeFunction_customType_notFound() {
+               // Register a custom type converter for Integer
+               var settings = Settings.create()
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .addTypeFunction(Integer.class, Integer::valueOf)
+                       .build();
+
+               var result = settings.get("nonexistent.property", 999);
+               assertEquals(999, result.intValue());
+       }
+
+       @Test
+       void v03_addTypeFunction_overridesDefault() {
+               // Register a custom converter for Boolean that inverts the 
value
+               var settings = Settings.create()
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .addTypeFunction(Boolean.class, s -> 
!Boolean.valueOf(s))
+                       .build();
+
+               System.setProperty(TEST_PROP, "true");
+               var result = settings.get(TEST_PROP, false);
+               // Should be inverted (true -> false)
+               assertFalse(result);
+       }
+
+       @Test
+       void v04_addTypeFunction_multipleTypes() {
+               // Register multiple custom type converters
+               var settings = Settings.create()
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .addTypeFunction(Integer.class, Integer::valueOf)
+                       .addTypeFunction(Long.class, Long::valueOf)
+                       .build();
+
+               System.setProperty(TEST_PROP, "123");
+               var intResult = settings.get(TEST_PROP, 0);
+               assertEquals(123, intResult.intValue());
+
+               System.setProperty(TEST_PROP_2, "456");
+               var longResult = settings.get(TEST_PROP_2, 0L);
+               assertEquals(456L, longResult.longValue());
+       }
+
+       @Test
+       void v05_addTypeFunction_customClass() {
+               // Create a custom class with a fromString method
+               // Using a static nested class to avoid local class issues
+               var settings = Settings.create()
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .addTypeFunction(String.class, s -> "custom-" + s)
+                       .build();
+
+               System.setProperty(TEST_PROP, "value");
+               // String is already supported, but we override it with a 
custom function
+               var result = settings.get(TEST_PROP, "default");
+               assertEquals("custom-value", result);
+       }
+
+       @Test
+       void v06_addTypeFunction_nullType() {
+               assertThrows(IllegalArgumentException.class, () -> {
+                       Settings.create().addTypeFunction(null, 
Integer::valueOf);
+               });
+       }
+
+       @Test
+       void v07_addTypeFunction_nullFunction() {
+               assertThrows(IllegalArgumentException.class, () -> {
+                       Settings.create().addTypeFunction(Integer.class, null);
+               });
+       }
+
+       @Test
+       void v08_addTypeFunction_unsupportedType() {
+               // Try to use a type that hasn't been registered
+               var settings = Settings.create()
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
+
+               System.setProperty(TEST_PROP, "123");
+               assertThrows(RuntimeException.class, () -> {
+                       settings.get(TEST_PROP, 0); // Integer not registered
+               });
+       }
+
+       @Test
+       void v09_addTypeFunction_usesDefaultWhenCustomNotRegistered() {
+               // Custom settings without Integer registered should fall back 
to default functions
+               // But Integer is not in DEFAULT_TYPE_FUNCTIONS, so it should 
throw
+               var settings = Settings.create()
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
+
+               // Boolean is in DEFAULT_TYPE_FUNCTIONS, so it should work
+               System.setProperty(TEST_PROP, "true");
+               var result = settings.get(TEST_PROP, false);
+               assertTrue(result);
+
+               // Integer is not in DEFAULT_TYPE_FUNCTIONS, so it should throw
+               System.setProperty(TEST_PROP_2, "123");
+               assertThrows(RuntimeException.class, () -> {
+                       settings.get(TEST_PROP_2, 0);
+               });
+       }
+
+       @Test
+       void v10_addTypeFunction_charsetUsesDefault() {
+               // Charset is in DEFAULT_TYPE_FUNCTIONS, so it should work 
without registration
+               // Use Charset.forName to get a Charset instance (not a 
concrete implementation)
+               var settings = Settings.create()
+                       .addSource(Settings.SYSTEM_PROPERTY_SOURCE)
+                       .addSource(Settings.SYSTEM_ENV_SOURCE)
+                       .build();
+
+               System.setProperty(TEST_PROP, "UTF-8");
+               var defaultCharset = Charset.forName("ISO-8859-1");
+               var result = settings.get(TEST_PROP, defaultCharset);
+               assertEquals(Charset.forName("UTF-8"), result);
+       }
 }
 


Reply via email to