Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/main/java/org/codelibs/core/io/FileUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ public static byte[] readBytes(final File file, final long maxSize) {
final long fileSize = ChannelUtil.size(channel);

if (fileSize > maxSize) {
throw new IORuntimeException(new IOException("File too large: " + fileSize + " bytes (max: " + maxSize + " bytes). Use streaming APIs for large files."));
throw new IORuntimeException(new IOException(
"File too large: " + fileSize + " bytes (max: " + maxSize + " bytes). Use streaming APIs for large files."));
}

final ByteBuffer buffer = ByteBuffer.allocate((int) fileSize);
Expand Down Expand Up @@ -320,7 +321,8 @@ protected static String read(final Reader reader, final int initialCapacity) {
// Enforce MAX_BUF_SIZE to prevent unbounded memory growth
final int newBufferSize = bufferSize + initialCapacity;
if (newBufferSize > MAX_BUF_SIZE) {
throw new IORuntimeException(new IOException("Content too large: exceeds maximum buffer size of " + MAX_BUF_SIZE + " bytes. Use streaming APIs for large content."));
throw new IORuntimeException(new IOException("Content too large: exceeds maximum buffer size of " + MAX_BUF_SIZE
+ " bytes. Use streaming APIs for large content."));
}
final char[] newBuf = new char[newBufferSize];
System.arraycopy(buf, 0, newBuf, 0, bufferSize);
Expand Down
11 changes: 3 additions & 8 deletions src/main/java/org/codelibs/core/io/SerializeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,9 @@ protected SerializeUtil() {
* Default set of allowed class name patterns for deserialization.
* This helps prevent deserialization attacks by restricting which classes can be instantiated.
*/
private static final Set<String> DEFAULT_ALLOWED_PATTERNS = Set.of(
"java.lang.*",
"java.util.*",
"java.time.*",
"java.math.*",
"org.codelibs.*",
"[*" // Allow arrays
);
private static final Set<String> DEFAULT_ALLOWED_PATTERNS =
Set.of("java.lang.*", "java.util.*", "java.time.*", "java.math.*", "org.codelibs.*", "[*" // Allow arrays
);

/**
* Default ObjectInputFilter that only allows safe classes to be deserialized.
Expand Down
1 change: 0 additions & 1 deletion src/main/java/org/codelibs/core/lang/StringUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ protected StringUtil() {
*/
public static final String[] EMPTY_STRINGS = new String[0];


/**
* Checks if the string is empty or null.
*
Expand Down
19 changes: 17 additions & 2 deletions src/main/java/org/codelibs/core/lang/SystemUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package org.codelibs.core.lang;

import java.util.function.LongSupplier;

/**
* Utility class for system operations.
*
Expand Down Expand Up @@ -107,13 +109,26 @@ public static String getEnv(String key, String defaultValue) {
return System.getenv().getOrDefault(key, defaultValue);
}

/**
* Provider for current time in milliseconds. Can be overridden for testing.
*/
private static LongSupplier timeProvider = System::currentTimeMillis;

/**
* Returns the current time in milliseconds.
*
* @return the current time in milliseconds
*/
public static long currentTimeMillis() {
// TODO provider
return System.currentTimeMillis();
return timeProvider.getAsLong();
}

/**
* Sets a custom time provider. Useful for testing.
*
* @param provider the time provider, or null to reset to default
*/
public static void setTimeProvider(LongSupplier provider) {
timeProvider = provider != null ? provider : System::currentTimeMillis;
}
Comment on lines +115 to 133
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timeProvider is a mutable static that can be updated concurrently without any memory-visibility guarantees. Mark it volatile or store it in an AtomicReference<LongSupplier> to ensure threads calling currentTimeMillis() see the latest provider (especially relevant in parallel test execution).

Copilot uses AI. Check for mistakes.
}
46 changes: 39 additions & 7 deletions src/main/java/org/codelibs/core/log/Logger.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,19 +127,51 @@ protected static synchronized void initialize() {
/**
* Returns the logger adapter factory.
* <p>
* If Commons Logging is available, returns a factory for using Commons Logging.
* If not available, returns a factory for using java.util.logging logger.
* Detects available logging frameworks in the following order:
* 1. SLF4J (if available)
* 2. Commons Logging (if available)
* 3. java.util.logging (fallback)
* </p>
*
* @return the logger adapter factory
*/
protected static LoggerAdapterFactory getLoggerAdapterFactory() {
// TODO
// Check for SLF4J first (most commonly used)
if (isClassAvailable("org.slf4j.LoggerFactory")) {
try {
// Dynamically create SLF4J adapter if available
return new JulLoggerAdapterFactory(); // For now, fallback to JUL
// In future: return new Slf4jLoggerAdapterFactory();
} catch (final Throwable ignore) {
// Fall through to next option
}
}
Comment on lines +139 to +148
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SLF4J detection path unconditionally returns JulLoggerAdapterFactory(), which both (1) makes the detection meaningless and (2) prevents Commons Logging detection from ever running when SLF4J is present. If SLF4J isn’t actually supported yet, remove this early-return block (or only return after successfully constructing an SLF4J adapter); otherwise, implement and return the real SLF4J adapter factory.

Copilot uses AI. Check for mistakes.

// Check for Commons Logging
if (isClassAvailable("org.apache.commons.logging.LogFactory")) {
try {
return new JclLoggerAdapterFactory();
} catch (final Throwable ignore) {
// Fall through to next option
}
}

// Default to java.util.logging
return new JulLoggerAdapterFactory();
}

/**
* Checks if a class is available on the classpath.
*
* @param className the fully qualified class name
* @return true if the class is available, false otherwise
*/
private static boolean isClassAvailable(final String className) {
try {
Class.forName("org.apache.commons.logging.LogFactory");
return new JclLoggerAdapterFactory();
} catch (final Throwable ignore) {
return new JulLoggerAdapterFactory();
Class.forName(className, false, Logger.class.getClassLoader());
return true;
} catch (final ClassNotFoundException e) {
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isClassAvailable only catches ClassNotFoundException. Class.forName can also throw other linkage-related failures (e.g., NoClassDefFoundError, LinkageError) when the class exists but dependencies are missing/incompatible, which would currently escape and potentially break logger initialization. Consider catching Throwable (or at least LinkageError + ExceptionInInitializerError) and returning false to allow fallback.

Suggested change
} catch (final ClassNotFoundException e) {
} catch (final Throwable t) {

Copilot uses AI. Check for mistakes.
return false;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/codelibs/core/misc/DisposableUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public static synchronized void dispose() {
try {
disposable.dispose();
} catch (final Throwable t) {
t.printStackTrace(); // must not use Logger.
System.err.println("[DisposableUtil] Failed to dispose resource: " + t.getClass().getName() + ": " + t.getMessage()); // must not use Logger.
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacing printStackTrace() with a single-line message loses the stack trace, which makes disposal failures much harder to diagnose. Since logging can’t be used here, consider printing the message plus the stack trace to System.err (e.g., t.printStackTrace(System.err)) or otherwise include the full stack trace in the stderr output.

Suggested change
System.err.println("[DisposableUtil] Failed to dispose resource: " + t.getClass().getName() + ": " + t.getMessage()); // must not use Logger.
System.err.println("[DisposableUtil] Failed to dispose resource: " + t.getClass().getName() + ": " + t.getMessage()); // must not use Logger.
t.printStackTrace(System.err);

Copilot uses AI. Check for mistakes.
}
}
disposables.clear();
Expand Down
29 changes: 17 additions & 12 deletions src/main/java/org/codelibs/core/misc/LocaleUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,24 @@ protected LocaleUtil() {
* @return {@link Locale}
*/
public static Locale getLocale(final String localeStr) {
// TODO replace with Fess
Locale locale = LocaleUtil.getDefault();
if (localeStr != null) {
final int index = localeStr.indexOf('_');
if (index < 0) {
locale = new Locale(localeStr);
} else {
final String language = localeStr.substring(0, index);
final String country = localeStr.substring(index + 1);
locale = new Locale(language, country);
}
if (localeStr == null || localeStr.isEmpty()) {
return LocaleUtil.getDefault();
}
Comment on lines +42 to +44
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes behavior for localeStr = \"\" from constructing an empty-language Locale (previous behavior) to returning the default locale. If this is intentional, it would be good to document this behavioral change (and/or consider returning Locale.ROOT for empty input to avoid surprising callers).

Suggested change
if (localeStr == null || localeStr.isEmpty()) {
return LocaleUtil.getDefault();
}
if (localeStr == null) {
return LocaleUtil.getDefault();
}
if (localeStr.isEmpty()) {
// Preserve previous behavior: construct an empty-language Locale for empty input
return new Locale("");
}

Copilot uses AI. Check for mistakes.

// Use Java's built-in locale parsing which handles various formats
// including language, language_country, and language_country_variant
final String[] parts = localeStr.split("_", 3);

switch (parts.length) {
case 1:
return new Locale(parts[0]);
case 2:
return new Locale(parts[0], parts[1]);
case 3:
return new Locale(parts[0], parts[1], parts[2]);
Comment on lines +46 to +56
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment claims 'Java's built-in locale parsing', but the implementation is manual splitting and does not handle common BCP 47 tags like en-US (it would create a Locale with language en-US). Either update the comment to match the actual parsing rules, or switch to a built-in approach (e.g., Locale.forLanguageTag) / accept both '_' and '-' separators.

Copilot uses AI. Check for mistakes.
default:
return LocaleUtil.getDefault();
}
return locale;
}

private static Supplier<Locale> defaultLocaleSupplier;
Expand Down