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);
+ }
}