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 b133217da9 Unit tests
b133217da9 is described below
commit b133217da9506431f3f4145bf8194b867f9b7fe8
Author: James Bognar <[email protected]>
AuthorDate: Mon Dec 1 18:20:55 2025 -0800
Unit tests
---
TODO.md | 3 +-
.../apache/juneau/common/collections/Cache.java | 5 +
.../apache/juneau/common/collections/Cache2.java | 2 +
.../apache/juneau/common/collections/Cache3.java | 2 +
.../apache/juneau/common/collections/Cache4.java | 2 +
.../apache/juneau/common/collections/Cache5.java | 2 +
.../juneau/common/collections/MapBuilder.java | 1 +
.../juneau/common/collections/SetBuilder.java | 1 +
.../java/org/apache/juneau/common/io/LocalDir.java | 1 +
.../juneau/common/reflect/AnnotationInfo.java | 1 +
.../juneau/common/reflect/AnnotationProvider.java | 1 +
.../apache/juneau/common/reflect/ClassInfo.java | 52 +-
.../juneau/common/reflect/ClassInfoTyped.java | 1 -
.../juneau/common/reflect/ParameterInfo.java | 2 +
.../apache/juneau/common/utils/StringFormat.java | 4 +-
.../apache/juneau/common/utils/StringUtils.java | 711 ++++++++++-----------
16 files changed, 406 insertions(+), 385 deletions(-)
diff --git a/TODO.md b/TODO.md
index 0c9bc4f0af..25c73573fb 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,6 +1,6 @@
# TODO List
-**Last generated TODO number: TODO-89**
+**Last generated TODO number: TODO-90**
This file tracks pending tasks for the Apache Juneau project. For completed
items, see [TODO-completed.md](TODO-completed.md).
@@ -18,6 +18,7 @@ This file tracks pending tasks for the Apache Juneau project.
For completed item
- [ ] TODO-27 Determine if there are any other good candidates for
Stringifiers and Listifiers.
- [ ] TODO-29 Finish setting up SonarQube analysis in git workflow.
- [ ] TODO-54 Search for places in code where Calendar should be replaced with
ZonedDateTime.
+- [ ] TODO-90 Investigate replacing `StringUtils.parseIsoCalendar()` with
java.time APIs and removing the helper if possible.
## Framework Improvements
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache.java
index 2e9556a08b..79da0eb180 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache.java
@@ -401,6 +401,7 @@ public class Cache<K,V> {
return cacheMode(WEAK);
}
}
+
/**
* Creates a new {@link Builder} for constructing a cache with explicit
type parameters.
*
@@ -424,6 +425,7 @@ public class Cache<K,V> {
public static <K,V> Builder<K,V> create() {
return new Builder<>();
}
+
/**
* Creates a new {@link Builder} for constructing a cache.
*
@@ -471,6 +473,7 @@ public class Cache<K,V> {
private final Function<K,V> supplier;
private final AtomicInteger cacheHits = new AtomicInteger();
+
/**
* Constructor.
*
@@ -509,6 +512,7 @@ public class Cache<K,V> {
shutdownMessage(() -> builder.id + ": hits=" +
cacheHits.get() + ", misses: " + size());
}
}
+
/**
* Removes all entries from the cache.
*/
@@ -516,6 +520,7 @@ public class Cache<K,V> {
getMap().clear();
getWrapperCache().clear(); // Clean up wrapper cache
}
+
/**
* Returns <jk>true</jk> if the cache contains a mapping for the
specified key.
*
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache2.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache2.java
index 1c2d6a03b1..d56ccdb5ac 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache2.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache2.java
@@ -335,6 +335,7 @@ public class Cache2<K1,K2,V> {
}
}
+
/**
* Creates a new {@link Builder} for constructing a cache with explicit
type parameters.
*
@@ -359,6 +360,7 @@ public class Cache2<K1,K2,V> {
public static <K1,K2,V> Builder<K1,K2,V> create() {
return new Builder<>();
}
+
/**
* Creates a new {@link Builder} for constructing a cache.
*
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache3.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache3.java
index 80ff1a288e..eab49a21d0 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache3.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache3.java
@@ -217,6 +217,7 @@ public class Cache3<K1,K2,K3,V> {
return cacheMode(WEAK);
}
}
+
/**
* Creates a new {@link Builder} for constructing a cache with explicit
type parameters.
*
@@ -233,6 +234,7 @@ public class Cache3<K1,K2,K3,V> {
public static <K1,K2,K3,V> Builder<K1,K2,K3,V> create() {
return new Builder<>();
}
+
/**
* Creates a new {@link Builder} for constructing a cache.
*
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache4.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache4.java
index 3e594c4ea8..d0450dcb77 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache4.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache4.java
@@ -204,6 +204,7 @@ public class Cache4<K1,K2,K3,K4,V> {
return cacheMode(WEAK);
}
}
+
/**
* Creates a new {@link Builder} for constructing a cache with explicit
type parameters.
*
@@ -221,6 +222,7 @@ public class Cache4<K1,K2,K3,K4,V> {
public static <K1,K2,K3,K4,V> Builder<K1,K2,K3,K4,V> create() {
return new Builder<>();
}
+
/**
* Creates a new {@link Builder} for constructing a cache.
*
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache5.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache5.java
index 7b5436644b..c499c21c12 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache5.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache5.java
@@ -207,6 +207,7 @@ public class Cache5<K1,K2,K3,K4,K5,V> {
return cacheMode(WEAK);
}
}
+
/**
* Creates a new {@link Builder} for constructing a cache with explicit
type parameters.
*
@@ -225,6 +226,7 @@ public class Cache5<K1,K2,K3,K4,K5,V> {
public static <K1,K2,K3,K4,K5,V> Builder<K1,K2,K3,K4,K5,V> create() {
return new Builder<>();
}
+
/**
* Creates a new {@link Builder} for constructing a cache.
*
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/MapBuilder.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/MapBuilder.java
index 1e86e36bae..113ff6bb3b 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/MapBuilder.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/MapBuilder.java
@@ -123,6 +123,7 @@ public class MapBuilder<K,V> {
public static <K,V> MapBuilder<K,V> create(Class<K> keyType, Class<V>
valueType) {
return new MapBuilder<>(assertArgNotNull("keyType", keyType),
assertArgNotNull("valueType", valueType));
}
+
private Map<K,V> map;
private boolean unmodifiable = false, sparse = false;
private Comparator<K> comparator;
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/SetBuilder.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/SetBuilder.java
index 6acaade43a..7829384847 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/SetBuilder.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/SetBuilder.java
@@ -119,6 +119,7 @@ public class SetBuilder<E> {
public static <E> SetBuilder<E> create(Class<E> elementType) {
return new SetBuilder<>(assertArgNotNull("elementType",
elementType));
}
+
private Set<E> set;
private boolean unmodifiable, sparse;
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/io/LocalDir.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/io/LocalDir.java
index 5cd98697cc..a2c6a2714f 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/io/LocalDir.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/io/LocalDir.java
@@ -105,6 +105,7 @@ public class LocalDir {
return true;
});
}
+
private final Class<?> clazz;
private final String clazzPath;
private final Path path;
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationInfo.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationInfo.java
index ba833e4988..3ecd7d111b 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationInfo.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationInfo.java
@@ -96,6 +96,7 @@ public class AnnotationInfo<T extends Annotation> {
.orElse(0);
// @formatter:on
}
+
private final Annotatable annotatable;
final int rank;
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
index 9d20b5c61c..ad0c472c9c 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
@@ -451,6 +451,7 @@ public class AnnotationProvider {
private static <A extends Annotation> AnnotationInfo<A> ai(Annotatable
on, A value) {
return AnnotationInfo.of(on, value);
}
+
private final Cache<Object,List<AnnotationInfo<Annotation>>>
runtimeCache;
private final Cache3<Class<?>,ElementInfo,AnnotationTraversal[],List>
cache;
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfo.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfo.java
index 9c35a6b5ab..6c78b3d618 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfo.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfo.java
@@ -119,27 +119,27 @@ public class ClassInfo extends ElementInfo implements
Annotatable {
/**
* Returns a class info wrapper around the specified class type.
*
- * @param <T> The class type.
* @param inner The class type.
+ * @param innerType The generic type (if parameterized type).
* @return The constructed class info.
*/
- public static <T> ClassInfoTyped<T> of(Class<T> inner) {
- return (ClassInfoTyped<T>)CACHE.get(inner, () -> new
ClassInfoTyped<>(inner));
+ public static ClassInfo of(Class<?> inner, Type innerType) {
+ if (inner == innerType)
+ return of(inner);
+ if (inner != null)
+ return new ClassInfoTyped<>(inner, innerType);
+ return new ClassInfo(null, innerType);
}
/**
* Returns a class info wrapper around the specified class type.
*
+ * @param <T> The class type.
* @param inner The class type.
- * @param innerType The generic type (if parameterized type).
* @return The constructed class info.
*/
- public static ClassInfo of(Class<?> inner, Type innerType) {
- if (inner == innerType)
- return of(inner);
- if (inner != null)
- return new ClassInfoTyped<>(inner, innerType);
- return new ClassInfo(null, innerType);
+ public static <T> ClassInfoTyped<T> of(Class<T> inner) {
+ return (ClassInfoTyped<T>)CACHE.get(inner, () -> new
ClassInfoTyped<>(inner));
}
/**
@@ -463,21 +463,21 @@ public class ClassInfo extends ElementInfo implements
Annotatable {
* @return The annotation if found, or <jk>null</jk> if not.
*/
- /**
- * Returns the component type of this class if it is an array
type.
- *
- * <p>
- * This is equivalent to {@link Class#getComponentType()} but
returns a {@link ClassInfo} instead.
- * Note that {@link #getComponentType()} also exists and
returns the base component type for multi-dimensional arrays.
- *
- * @return The {@link ClassInfo} representing the component
type, or <jk>null</jk> if this class does not represent an array type.
- */
- public ClassInfo componentType() {
- if (inner == null)
- return null;
- var ct = inner.componentType();
- return ct == null ? null : of(ct);
- }
+ /**
+ * Returns the component type of this class if it is an array type.
+ *
+ * <p>
+ * This is equivalent to {@link Class#getComponentType()} but returns a
{@link ClassInfo} instead.
+ * Note that {@link #getComponentType()} also exists and returns the
base component type for multi-dimensional arrays.
+ *
+ * @return The {@link ClassInfo} representing the component type, or
<jk>null</jk> if this class does not represent an array type.
+ */
+ public ClassInfo componentType() {
+ if (inner == null)
+ return null;
+ var ct = inner.componentType();
+ return ct == null ? null : of(ct);
+ }
/**
* Returns the descriptor string of this class.
@@ -711,7 +711,7 @@ public class ClassInfo extends ElementInfo implements
Annotatable {
*
* @return The class loader for this class, or <jk>null</jk> if it
doesn't have one.
*/
-public ClassLoader getClassLoader() { return inner == null ? null :
inner.getClassLoader(); }
+ public ClassLoader getClassLoader() { return inner == null ? null :
inner.getClassLoader(); }
/**
* Returns the base component type of this class.
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfoTyped.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfoTyped.java
index fe5994b648..eb340935a2 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfoTyped.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfoTyped.java
@@ -45,4 +45,3 @@ public class ClassInfoTyped<T> extends ClassInfo {
}
}
-
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
index c416710cb4..d6c08a23e9 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ParameterInfo.java
@@ -135,9 +135,11 @@ public class ParameterInfo extends ElementInfo implements
Annotatable {
}
throw new IllegalArgumentException("Parameter not found in
declaring executable: " + inner);
}
+
static void reset() {
DISABLE_PARAM_NAME_DETECTION.reset();
}
+
private final ExecutableInfo executable;
private final Parameter inner;
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringFormat.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringFormat.java
index 8de20c61d2..50b0805484 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringFormat.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringFormat.java
@@ -385,7 +385,8 @@ public final class StringFormat {
private static final Cache2<Locale,String,MessageFormat>
MESSAGE_FORMAT_CACHE = Cache2.of(Locale.class, String.class,
MessageFormat.class).maxSize(100).threadLocal().cacheMode(CACHE_MODE)
.supplier((locale, content) -> new MessageFormat(content,
locale)).build();
- private static final Cache<Locale,NumberFormat> NUMBER_FORMAT_CACHE =
Cache.of(Locale.class,
NumberFormat.class).maxSize(50).threadLocal().cacheMode(CACHE_MODE).supplier(NumberFormat::getInstance).build();
+ private static final Cache<Locale,NumberFormat> NUMBER_FORMAT_CACHE =
Cache.of(Locale.class,
NumberFormat.class).maxSize(50).threadLocal().cacheMode(CACHE_MODE).supplier(NumberFormat::getInstance)
+ .build();
private static final Cache<Locale,DateFormat> DATE_FORMAT_CACHE =
Cache.of(Locale.class,
DateFormat.class).maxSize(50).threadLocal().cacheMode(CACHE_MODE)
.supplier(locale ->
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT,
locale)).build();
@@ -452,6 +453,7 @@ public final class StringFormat {
return;
tokens.add(new LiteralToken(pattern.substring(start)));
}
+
private static void lit(List<Token> tokens, String pattern, int start,
int end) {
if (start == end)
return;
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringUtils.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringUtils.java
index 09060bd783..8288749f38 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringUtils.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringUtils.java
@@ -181,166 +181,6 @@ public class StringUtils {
return in.substring(0, length - 3) + "...";
}
- private static List<Tuple2<Class<?>,Function<Object,String>>>
loadReadifiers() {
- var list = new
ArrayList<Tuple2<Class<?>,Function<Object,String>>>();
-
- // More specific types first - order matters!
-
- // Map.Entry before Map
- list.add(Tuple2.of(Map.Entry.class, o -> {
- var e = (Map.Entry<?,?>)o;
- return readable(e.getKey()) + '=' +
readable(e.getValue());
- }));
-
- // Collection before Iterable
- list.add(Tuple2.of(Collection.class, o -> {
- var c = (Collection<?>)o;
- return
c.stream().map(StringUtils::readable).collect(joining(",", "[", "]"));
- }));
-
- // Map
- list.add(Tuple2.of(Map.class, o -> {
- var m = (Map<?,?>)o;
- return
m.entrySet().stream().map(StringUtils::readable).collect(joining(",", "{",
"}"));
- }));
-
- // Iterable (but not Collection, which is handled above)
- list.add(Tuple2.of(Iterable.class, o -> {
- var i = (Iterable<?>)o;
- return readable(toList(i));
- }));
-
- // Iterator
- list.add(Tuple2.of(Iterator.class, o -> {
- var i = (Iterator<?>)o;
- return readable(toList(i));
- }));
-
- // Enumeration
- list.add(Tuple2.of(Enumeration.class, o -> {
- var e = (Enumeration<?>)o;
- return readable(toList(e));
- }));
-
- // Optional
- list.add(Tuple2.of(Optional.class, o -> {
- var opt = (Optional<?>)o;
- return readable(opt.orElse(null));
- }));
-
- // GregorianCalendar
- list.add(Tuple2.of(GregorianCalendar.class, o -> {
- var cal = (GregorianCalendar)o;
- return
cal.toZonedDateTime().format(DateTimeFormatter.ISO_INSTANT);
- }));
-
- // Date
- list.add(Tuple2.of(Date.class, o -> {
- var date = (Date)o;
- return date.toInstant().toString();
- }));
-
- // InputStream
- list.add(Tuple2.of(InputStream.class, o -> {
- var is = (InputStream)o;
- return toHex(is);
- }));
-
- // Reader
- list.add(Tuple2.of(Reader.class, o -> {
- var r = (Reader)o;
- return safe(() -> read(r));
- }));
-
- // File
- list.add(Tuple2.of(File.class, o -> {
- var f = (File)o;
- return safe(() -> read(f));
- }));
-
- // byte[]
- list.add(Tuple2.of(byte[].class, o -> {
- var bytes = (byte[])o;
- return toHex(bytes);
- }));
-
- // Enum
- list.add(Tuple2.of(Enum.class, o -> {
- var e = (Enum<?>)o;
- return e.name();
- }));
-
- // Class
- list.add(Tuple2.of(Class.class, o -> {
- var c = (Class<?>)o;
- return cns(c);
- }));
-
- // Executable (Method or Constructor)
- list.add(Tuple2.of(Executable.class, o -> {
- var exec = (Executable)o;
- var sb = new StringBuilder(64);
- sb.append(exec instanceof Constructor ?
cns(exec.getDeclaringClass()) : exec.getName()).append('(');
- var pt = exec.getParameterTypes();
- for (var i = 0; i < pt.length; i++) {
- if (i > 0)
- sb.append(',');
- sb.append(cns(pt[i]));
- }
- sb.append(')');
- return sb.toString();
- }));
-
- // ClassInfo
- list.add(Tuple2.of(ClassInfo.class, o -> {
- var ci = (ClassInfo)o;
- return ci.toString();
- }));
-
- // ExecutableInfo
- list.add(Tuple2.of(ExecutableInfo.class, o -> {
- var ei = (ExecutableInfo)o;
- return ei.toString();
- }));
-
- // FieldInfo
- list.add(Tuple2.of(FieldInfo.class, o -> {
- var fi = (FieldInfo)o;
- return fi.toString();
- }));
-
- // ParameterInfo
- list.add(Tuple2.of(ParameterInfo.class, o -> {
- var pi = (ParameterInfo)o;
- return pi.toString();
- }));
-
- // Field
- list.add(Tuple2.of(Field.class, o -> {
- var f = (Field)o;
- return cns(f.getDeclaringClass()) + "." + f.getName();
- }));
-
- // Parameter
- list.add(Tuple2.of(Parameter.class, o -> {
- var p = (Parameter)o;
- var exec = p.getDeclaringExecutable();
- var sb = new StringBuilder(64);
- sb.append(exec instanceof Constructor ?
cns(exec.getDeclaringClass()) : exec.getName()).append('[');
- var params = exec.getParameters();
- for (var i = 0; i < params.length; i++) {
- if (params[i] == p) {
- sb.append(i);
- break;
- }
- }
- sb.append(']');
- return sb.toString();
- }));
-
- return Collections.unmodifiableList(list);
- }
-
/**
* Appends a string to a StringBuilder, creating a new one if null.
*
@@ -3823,172 +3663,66 @@ public class StringUtils {
}
/**
- * Validates if a string is a valid IPv6 address format (without
network operations).
+ * Validates if a string is a valid MAC address.
*
* <p>
- * This method performs pure string-based validation and does not
perform any DNS lookups
- * or network operations, making it fast and suitable for validation
purposes.
+ * Supports common MAC address formats:
+ * <ul>
+ * <li>Colon-separated: <js>"00:1B:44:11:3A:B7"</js></li>
+ * <li>Hyphen-separated: <js>"00-1B-44-11-3A-B7"</js></li>
+ * <li>No separators: <js>"001B44113AB7"</js></li>
+ * </ul>
*
- * @param ip The IPv6 address string to validate.
- * @return <jk>true</jk> if the string is a valid IPv6 address format,
<jk>false</jk> otherwise.
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * isValidMacAddress(<js>"00:1B:44:11:3A:B7"</js>); <jc>//
true</jc>
+ * isValidMacAddress(<js>"00-1B-44-11-3A-B7"</js>); <jc>//
true</jc>
+ * isValidMacAddress(<js>"001B44113AB7"</js>); <jc>//
true</jc>
+ * isValidMacAddress(<js>"00:1B:44:11:3A"</js>); <jc>// false
(too short)</jc>
+ * </p>
+ *
+ * @param mac The MAC address string to validate. Can be <jk>null</jk>.
+ * @return <jk>true</jk> if the string is a valid MAC address,
<jk>false</jk> otherwise.
*/
- private static boolean isValidIPv6Address(String ip) {
- // IPv6 addresses can be:
- // 1. Full format: 2001:0db8:85a3:0000:0000:8a2e:0370:7334 (8
groups of 4 hex digits)
- // 2. Compressed format: 2001:db8::1 (uses :: to represent
consecutive zeros)
- // 3. IPv4-mapped: ::ffff:192.168.1.1 (last 32 bits as IPv4)
- // 4. Loopback: ::1
- // 5. Unspecified: ::
-
- // Cannot start or end with a single colon (except ::)
- if (ip.startsWith(":") && !ip.startsWith("::"))
+ public static boolean isValidMacAddress(String mac) {
+ if (isEmpty(mac))
return false;
- if (ip.endsWith(":") && !ip.endsWith("::"))
+
+ // Remove separators and check if it's 12 hex digits
+ var cleaned = mac.replaceAll("[:-]", "").toUpperCase();
+ if (cleaned.length() != 12)
return false;
- // Check for IPv4-mapped format (contains both : and .)
- if (ip.contains(".")) {
- // Must be in format ::ffff:x.x.x.x or similar
- var lastColon = ip.lastIndexOf(":");
- if (lastColon < 0)
- return false;
- var ipv4Part = ip.substring(lastColon + 1);
- // Validate IPv4 part
- var ipv4Parts = ipv4Part.split("\\.");
- if (ipv4Parts.length != 4)
- return false;
- for (var part : ipv4Parts) {
- try {
- var num = Integer.parseInt(part);
- if (num < 0 || num > 255)
- return false;
- } catch (@SuppressWarnings("unused")
NumberFormatException e) {
- return false;
- }
- }
- // Validate IPv6 part before the IPv4
- var ipv6Part = ip.substring(0, lastColon);
- if (ipv6Part.isEmpty() || ipv6Part.equals("::ffff") ||
ipv6Part.equals("::FFFF"))
- return true;
- // More complex validation would be needed for other
IPv4-mapped formats
- // For now, accept common formats
+ // Check if all characters are valid hex digits
+ return cleaned.matches("^[0-9A-F]{12}$");
+ }
+
+ /**
+ * Validates if a string is a valid regular expression pattern.
+ *
+ * <p>
+ * Attempts to compile the regex pattern to verify it's syntactically
correct.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * isValidRegex(<js>"[a-z]+"</js>); <jc>// true</jc>
+ * isValidRegex(<js>"[a-z"</js>); <jc>// false (unclosed
bracket)</jc>
+ * isValidRegex(<js>"(test"</js>); <jc>// false (unclosed
parenthesis)</jc>
+ * </p>
+ *
+ * @param regex The regex pattern to validate. Can be <jk>null</jk>.
+ * @return <jk>true</jk> if the string is a valid regex pattern,
<jk>false</jk> otherwise.
+ */
+ public static boolean isValidRegex(String regex) {
+ if (isEmpty(regex))
+ return false;
+ try {
+ Pattern.compile(regex);
+ return true;
+ } catch (@SuppressWarnings("unused") PatternSyntaxException e) {
+ return false;
}
-
- // Check for :: (compression) - only one allowed
- var doubleColonCount = 0;
- for (var i = 1; i < ip.length(); i++) {
- if (ip.charAt(i) == ':' && ip.charAt(i - 1) == ':') {
- doubleColonCount++;
- if (doubleColonCount > 1)
- return false; // Only one :: allowed
- }
- }
-
- // Split by ::
- var parts = ip.split("::", -1);
- if (parts.length > 2)
- return false; // Only one :: allowed
-
- if (parts.length == 2) {
- // Compressed format
- var leftParts = parts[0].isEmpty() ? new String[0] :
parts[0].split(":");
- var rightParts = parts[1].isEmpty() ? new String[0] :
parts[1].split(":");
- var totalParts = leftParts.length + rightParts.length;
- if (totalParts > 7)
- return false; // Too many groups (max 8, but ::
counts as one or more)
- if (totalParts == 0 && !ip.equals("::"))
- return false; // Empty on both sides of :: is
invalid (except :: itself)
- } else {
- // Full format (no compression)
- var groups = ip.split(":");
- if (groups.length != 8)
- return false;
- }
-
- // Validate each hex group
- var groups = ip.split("::");
- for (var groupSection : groups) {
- if (groupSection.isEmpty())
- continue; // Skip empty section from ::
- var groupParts = groupSection.split(":");
- for (var group : groupParts) {
- if (group.isEmpty())
- return false;
- if (group.length() > 4)
- return false; // Each group is max 4
hex digits
- // Validate hex digits
- for (var i = 0; i < group.length(); i++) {
- var c = group.charAt(i);
- if (!((c >= '0' && c <= '9') || (c >=
'a' && c <= 'f') || (c >= 'A' && c <= 'F')))
- return false;
- }
- }
- }
-
- return true;
- }
-
- /**
- * Validates if a string is a valid MAC address.
- *
- * <p>
- * Supports common MAC address formats:
- * <ul>
- * <li>Colon-separated: <js>"00:1B:44:11:3A:B7"</js></li>
- * <li>Hyphen-separated: <js>"00-1B-44-11-3A-B7"</js></li>
- * <li>No separators: <js>"001B44113AB7"</js></li>
- * </ul>
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- * isValidMacAddress(<js>"00:1B:44:11:3A:B7"</js>); <jc>//
true</jc>
- * isValidMacAddress(<js>"00-1B-44-11-3A-B7"</js>); <jc>//
true</jc>
- * isValidMacAddress(<js>"001B44113AB7"</js>); <jc>//
true</jc>
- * isValidMacAddress(<js>"00:1B:44:11:3A"</js>); <jc>// false
(too short)</jc>
- * </p>
- *
- * @param mac The MAC address string to validate. Can be <jk>null</jk>.
- * @return <jk>true</jk> if the string is a valid MAC address,
<jk>false</jk> otherwise.
- */
- public static boolean isValidMacAddress(String mac) {
- if (isEmpty(mac))
- return false;
-
- // Remove separators and check if it's 12 hex digits
- var cleaned = mac.replaceAll("[:-]", "").toUpperCase();
- if (cleaned.length() != 12)
- return false;
-
- // Check if all characters are valid hex digits
- return cleaned.matches("^[0-9A-F]{12}$");
- }
-
- /**
- * Validates if a string is a valid regular expression pattern.
- *
- * <p>
- * Attempts to compile the regex pattern to verify it's syntactically
correct.
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- * isValidRegex(<js>"[a-z]+"</js>); <jc>// true</jc>
- * isValidRegex(<js>"[a-z"</js>); <jc>// false (unclosed
bracket)</jc>
- * isValidRegex(<js>"(test"</js>); <jc>// false (unclosed
parenthesis)</jc>
- * </p>
- *
- * @param regex The regex pattern to validate. Can be <jk>null</jk>.
- * @return <jk>true</jk> if the string is a valid regex pattern,
<jk>false</jk> otherwise.
- */
- public static boolean isValidRegex(String regex) {
- if (isEmpty(regex))
- return false;
- try {
- Pattern.compile(regex);
- return true;
- } catch (@SuppressWarnings("unused") PatternSyntaxException e) {
- return false;
- }
- }
+ }
/**
* Validates if a time string matches the specified time format.
@@ -4427,12 +4161,6 @@ public class StringUtils {
return str.substring(0, len);
}
- // TODO: See if we can remove StringUtils.parseIsoCalendar.
- // Currently used by:
- // - OpenApiParserSession.java for DATE/DATE_TIME format parsing
- // - StringUtils.parseIsoDate() (which wraps this method)
- // Investigation needed: Can we replace this with java.time APIs or
other standard date parsing?
-
/**
* Calculates the Levenshtein distance (edit distance) between two
strings.
*
@@ -4522,6 +4250,12 @@ public class StringUtils {
return count;
}
+ // TODO: See if we can remove StringUtils.parseIsoCalendar.
+ // Currently used by:
+ // - OpenApiParserSession.java for DATE/DATE_TIME format parsing
+ // - StringUtils.parseIsoDate() (which wraps this method)
+ // Investigation needed: Can we replace this with java.time APIs or
other standard date parsing?
+
/**
* Null-safe convenience method for {@link String#toLowerCase()}.
*
@@ -5177,10 +4911,6 @@ public class StringUtils {
return ! containsAny(s, values);
}
-
//-----------------------------------------------------------------------------------------------------------------
- // String validation methods
-
//-----------------------------------------------------------------------------------------------------------------
-
/**
* Returns the specified string, or <jk>null</jk> if that string is
<jk>null</jk> or empty.
*
@@ -5203,6 +4933,10 @@ public class StringUtils {
return s.substring(0, 1) + s.substring(1).replaceAll(".", "*");
// NOSONAR
}
+
//-----------------------------------------------------------------------------------------------------------------
+ // String validation methods
+
//-----------------------------------------------------------------------------------------------------------------
+
/**
* Provides optimization suggestions for a string based on its
characteristics.
*
@@ -5403,10 +5137,6 @@ public class StringUtils {
return Float.parseFloat(StringUtils.removeUnderscores(value));
}
-
//-----------------------------------------------------------------------------------------------------------------
- // String manipulation methods
-
//-----------------------------------------------------------------------------------------------------------------
-
/**
* Same as {@link Integer#parseInt(String)} but removes any underscore
characters first.
*
@@ -5446,10 +5176,17 @@ public class StringUtils {
return Integer.decode(s.substring(0, s.length() - 1).trim()) *
m; // NOSONAR - NPE not possible here.
}
+
//-----------------------------------------------------------------------------------------------------------------
+ // String manipulation methods
+
//-----------------------------------------------------------------------------------------------------------------
+
/**
* Parses an ISO8601 string into a calendar.
*
* <p>
+ * TODO-90: Investigate whether this helper can be removed in favor of
java.time parsing (see TODO.md).
+ *
+ * <p>
* Supports any of the following formats:
* <br><c>yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddThh, yyyy-MM-ddThh:mm,
yyyy-MM-ddThh:mm:ss, yyyy-MM-ddThh:mm:ss.SSS</c>
*
@@ -5903,10 +5640,6 @@ public class StringUtils {
return sb.toString();
}
-
//-----------------------------------------------------------------------------------------------------------------
- // String joining and splitting methods
-
//-----------------------------------------------------------------------------------------------------------------
-
/**
* Generates a random numeric string of the specified length.
*
@@ -5931,10 +5664,6 @@ public class StringUtils {
return sb.toString();
}
-
//-----------------------------------------------------------------------------------------------------------------
- // String cleaning and sanitization methods
-
//-----------------------------------------------------------------------------------------------------------------
-
/**
* Generates a random string of the specified length using characters
from the given character set.
*
@@ -5964,6 +5693,10 @@ public class StringUtils {
return sb.toString();
}
+
//-----------------------------------------------------------------------------------------------------------------
+ // String joining and splitting methods
+
//-----------------------------------------------------------------------------------------------------------------
+
/**
* Calculates a simple readability score for a string.
*
@@ -6022,6 +5755,10 @@ public class StringUtils {
return Math.max(0.0, Math.min(100.0, score));
}
+
//-----------------------------------------------------------------------------------------------------------------
+ // String cleaning and sanitization methods
+
//-----------------------------------------------------------------------------------------------------------------
+
/**
* Converts an arbitrary object to a readable string format suitable
for debugging and testing.
*
@@ -7558,10 +7295,6 @@ public class StringUtils {
return sb.toString();
}
-
//------------------------------------------------------------------------------------------------------------------
- // Additional utility methods
-
//------------------------------------------------------------------------------------------------------------------
-
/**
* Same as {@link #toHex(byte[])} but puts spaces between the byte
strings.
*
@@ -7589,6 +7322,10 @@ public class StringUtils {
return obj == null ? null : obj.toString();
}
+
//------------------------------------------------------------------------------------------------------------------
+ // Additional utility methods
+
//------------------------------------------------------------------------------------------------------------------
+
/**
* Safely converts an object to a string, returning the default string
if the object is <jk>null</jk>.
*
@@ -7879,10 +7616,6 @@ public class StringUtils {
return str.replace("<", "<").replace(">",
">").replace(""", "\"").replace("'", "'").replace("'",
"'").replace("&", "&");
}
-
//-----------------------------------------------------------------------------------------------------------------
- // String Array and Collection Utilities
-
//-----------------------------------------------------------------------------------------------------------------
-
/**
* Unescapes XML entities in a string.
*
@@ -8025,10 +7758,6 @@ public class StringUtils {
return s;
}
-
//-----------------------------------------------------------------------------------------------------------------
- // String Builder Utilities
-
//-----------------------------------------------------------------------------------------------------------------
-
/**
* Similar to {@link URLEncoder#encode(String, String)} but doesn't
encode <js>"/"</js> characters.
*
@@ -8138,6 +7867,10 @@ public class StringUtils {
return count;
}
+
//-----------------------------------------------------------------------------------------------------------------
+ // String Builder Utilities
+
//-----------------------------------------------------------------------------------------------------------------
+
/**
* Wraps text to a specified line length.
*
@@ -8280,10 +8013,6 @@ public class StringUtils {
return result.toString();
}
-
//-----------------------------------------------------------------------------------------------------------------
- // Performance and Memory Utilities
-
//-----------------------------------------------------------------------------------------------------------------
-
/**
* Helper method to estimate the number of syllables in a word.
*/
@@ -8338,6 +8067,10 @@ public class StringUtils {
}
}
+
//-----------------------------------------------------------------------------------------------------------------
+ // Performance and Memory Utilities
+
//-----------------------------------------------------------------------------------------------------------------
+
/**
* Helper method to get Soundex code for a character.
*/
@@ -8359,6 +8092,112 @@ public class StringUtils {
return '0'; // Non-letter characters
}
+ /**
+ * Validates if a string is a valid IPv6 address format (without
network operations).
+ *
+ * <p>
+ * This method performs pure string-based validation and does not
perform any DNS lookups
+ * or network operations, making it fast and suitable for validation
purposes.
+ *
+ * @param ip The IPv6 address string to validate.
+ * @return <jk>true</jk> if the string is a valid IPv6 address format,
<jk>false</jk> otherwise.
+ */
+ private static boolean isValidIPv6Address(String ip) {
+ // IPv6 addresses can be:
+ // 1. Full format: 2001:0db8:85a3:0000:0000:8a2e:0370:7334 (8
groups of 4 hex digits)
+ // 2. Compressed format: 2001:db8::1 (uses :: to represent
consecutive zeros)
+ // 3. IPv4-mapped: ::ffff:192.168.1.1 (last 32 bits as IPv4)
+ // 4. Loopback: ::1
+ // 5. Unspecified: ::
+
+ // Cannot start or end with a single colon (except ::)
+ if (ip.startsWith(":") && !ip.startsWith("::"))
+ return false;
+ if (ip.endsWith(":") && !ip.endsWith("::"))
+ return false;
+
+ // Check for IPv4-mapped format (contains both : and .)
+ if (ip.contains(".")) {
+ // Must be in format ::ffff:x.x.x.x or similar
+ var lastColon = ip.lastIndexOf(":");
+ if (lastColon < 0)
+ return false;
+ var ipv4Part = ip.substring(lastColon + 1);
+ // Validate IPv4 part
+ var ipv4Parts = ipv4Part.split("\\.");
+ if (ipv4Parts.length != 4)
+ return false;
+ for (var part : ipv4Parts) {
+ try {
+ var num = Integer.parseInt(part);
+ if (num < 0 || num > 255)
+ return false;
+ } catch (@SuppressWarnings("unused")
NumberFormatException e) {
+ return false;
+ }
+ }
+ // Validate IPv6 part before the IPv4
+ var ipv6Part = ip.substring(0, lastColon);
+ if (ipv6Part.isEmpty() || ipv6Part.equals("::ffff") ||
ipv6Part.equals("::FFFF"))
+ return true;
+ // More complex validation would be needed for other
IPv4-mapped formats
+ // For now, accept common formats
+ }
+
+ // Check for :: (compression) - only one allowed
+ var doubleColonCount = 0;
+ for (var i = 1; i < ip.length(); i++) {
+ if (ip.charAt(i) == ':' && ip.charAt(i - 1) == ':') {
+ doubleColonCount++;
+ if (doubleColonCount > 1)
+ return false; // Only one :: allowed
+ }
+ }
+
+ // Split by ::
+ var parts = ip.split("::", -1);
+ if (parts.length > 2)
+ return false; // Only one :: allowed
+
+ if (parts.length == 2) {
+ // Compressed format
+ var leftParts = parts[0].isEmpty() ? new String[0] :
parts[0].split(":");
+ var rightParts = parts[1].isEmpty() ? new String[0] :
parts[1].split(":");
+ var totalParts = leftParts.length + rightParts.length;
+ if (totalParts > 7)
+ return false; // Too many groups (max 8, but ::
counts as one or more)
+ if (totalParts == 0 && !ip.equals("::"))
+ return false; // Empty on both sides of :: is
invalid (except :: itself)
+ } else {
+ // Full format (no compression)
+ var groups = ip.split(":");
+ if (groups.length != 8)
+ return false;
+ }
+
+ // Validate each hex group
+ var groups = ip.split("::");
+ for (var groupSection : groups) {
+ if (groupSection.isEmpty())
+ continue; // Skip empty section from ::
+ var groupParts = groupSection.split(":");
+ for (var group : groupParts) {
+ if (group.isEmpty())
+ return false;
+ if (group.length() > 4)
+ return false; // Each group is max 4
hex digits
+ // Validate hex digits
+ for (var i = 0; i < group.length(); i++) {
+ var c = group.charAt(i);
+ if (!((c >= '0' && c <= '9') || (c >=
'a' && c <= 'f') || (c >= 'A' && c <= 'F')))
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
/**
* Helper method to check if a character is a vowel.
*/
@@ -8366,6 +8205,166 @@ public class StringUtils {
return c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U';
}
+ private static List<Tuple2<Class<?>,Function<Object,String>>>
loadReadifiers() {
+ var list = new
ArrayList<Tuple2<Class<?>,Function<Object,String>>>();
+
+ // More specific types first - order matters!
+
+ // Map.Entry before Map
+ list.add(Tuple2.of(Map.Entry.class, o -> {
+ var e = (Map.Entry<?,?>)o;
+ return readable(e.getKey()) + '=' +
readable(e.getValue());
+ }));
+
+ // Collection before Iterable
+ list.add(Tuple2.of(Collection.class, o -> {
+ var c = (Collection<?>)o;
+ return
c.stream().map(StringUtils::readable).collect(joining(",", "[", "]"));
+ }));
+
+ // Map
+ list.add(Tuple2.of(Map.class, o -> {
+ var m = (Map<?,?>)o;
+ return
m.entrySet().stream().map(StringUtils::readable).collect(joining(",", "{",
"}"));
+ }));
+
+ // Iterable (but not Collection, which is handled above)
+ list.add(Tuple2.of(Iterable.class, o -> {
+ var i = (Iterable<?>)o;
+ return readable(toList(i));
+ }));
+
+ // Iterator
+ list.add(Tuple2.of(Iterator.class, o -> {
+ var i = (Iterator<?>)o;
+ return readable(toList(i));
+ }));
+
+ // Enumeration
+ list.add(Tuple2.of(Enumeration.class, o -> {
+ var e = (Enumeration<?>)o;
+ return readable(toList(e));
+ }));
+
+ // Optional
+ list.add(Tuple2.of(Optional.class, o -> {
+ var opt = (Optional<?>)o;
+ return readable(opt.orElse(null));
+ }));
+
+ // GregorianCalendar
+ list.add(Tuple2.of(GregorianCalendar.class, o -> {
+ var cal = (GregorianCalendar)o;
+ return
cal.toZonedDateTime().format(DateTimeFormatter.ISO_INSTANT);
+ }));
+
+ // Date
+ list.add(Tuple2.of(Date.class, o -> {
+ var date = (Date)o;
+ return date.toInstant().toString();
+ }));
+
+ // InputStream
+ list.add(Tuple2.of(InputStream.class, o -> {
+ var is = (InputStream)o;
+ return toHex(is);
+ }));
+
+ // Reader
+ list.add(Tuple2.of(Reader.class, o -> {
+ var r = (Reader)o;
+ return safe(() -> read(r));
+ }));
+
+ // File
+ list.add(Tuple2.of(File.class, o -> {
+ var f = (File)o;
+ return safe(() -> read(f));
+ }));
+
+ // byte[]
+ list.add(Tuple2.of(byte[].class, o -> {
+ var bytes = (byte[])o;
+ return toHex(bytes);
+ }));
+
+ // Enum
+ list.add(Tuple2.of(Enum.class, o -> {
+ var e = (Enum<?>)o;
+ return e.name();
+ }));
+
+ // Class
+ list.add(Tuple2.of(Class.class, o -> {
+ var c = (Class<?>)o;
+ return cns(c);
+ }));
+
+ // Executable (Method or Constructor)
+ list.add(Tuple2.of(Executable.class, o -> {
+ var exec = (Executable)o;
+ var sb = new StringBuilder(64);
+ sb.append(exec instanceof Constructor ?
cns(exec.getDeclaringClass()) : exec.getName()).append('(');
+ var pt = exec.getParameterTypes();
+ for (var i = 0; i < pt.length; i++) {
+ if (i > 0)
+ sb.append(',');
+ sb.append(cns(pt[i]));
+ }
+ sb.append(')');
+ return sb.toString();
+ }));
+
+ // ClassInfo
+ list.add(Tuple2.of(ClassInfo.class, o -> {
+ var ci = (ClassInfo)o;
+ return ci.toString();
+ }));
+
+ // ExecutableInfo
+ list.add(Tuple2.of(ExecutableInfo.class, o -> {
+ var ei = (ExecutableInfo)o;
+ return ei.toString();
+ }));
+
+ // FieldInfo
+ list.add(Tuple2.of(FieldInfo.class, o -> {
+ var fi = (FieldInfo)o;
+ return fi.toString();
+ }));
+
+ // ParameterInfo
+ list.add(Tuple2.of(ParameterInfo.class, o -> {
+ var pi = (ParameterInfo)o;
+ return pi.toString();
+ }));
+
+ // Field
+ list.add(Tuple2.of(Field.class, o -> {
+ var f = (Field)o;
+ return cns(f.getDeclaringClass()) + "." + f.getName();
+ }));
+
+ // Parameter
+ list.add(Tuple2.of(Parameter.class, o -> {
+ var p = (Parameter)o;
+ var exec = p.getDeclaringExecutable();
+ var sb = new StringBuilder(64);
+ sb.append(exec instanceof Constructor ?
cns(exec.getDeclaringClass()) : exec.getName()).append('[');
+ var params = exec.getParameters();
+ for (var i = 0; i < params.length; i++) {
+ if (params[i] == p) {
+ sb.append(i);
+ break;
+ }
+ }
+ sb.append(']');
+ return sb.toString();
+ }));
+
+ return Collections.unmodifiableList(list);
+ }
+
/**
* Determines the multiplier value based on the suffix character in a
string.
*