From 3bbdd2a08fbe259fdeee719329004acabe1355c5 Mon Sep 17 00:00:00 2001 From: Shinsuke Sugaya Date: Wed, 11 Feb 2026 22:50:11 +0900 Subject: [PATCH] Improve utility behavior and logger adapter detection --- .../java/org/codelibs/core/io/FileUtil.java | 6 ++- .../org/codelibs/core/io/SerializeUtil.java | 11 ++--- .../org/codelibs/core/lang/StringUtil.java | 1 - .../org/codelibs/core/lang/SystemUtil.java | 19 +++++++- .../java/org/codelibs/core/log/Logger.java | 46 ++++++++++++++++--- .../codelibs/core/misc/DisposableUtil.java | 2 +- .../org/codelibs/core/misc/LocaleUtil.java | 29 +++++++----- 7 files changed, 81 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/codelibs/core/io/FileUtil.java b/src/main/java/org/codelibs/core/io/FileUtil.java index 1eb67e3..3198f78 100644 --- a/src/main/java/org/codelibs/core/io/FileUtil.java +++ b/src/main/java/org/codelibs/core/io/FileUtil.java @@ -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); @@ -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); diff --git a/src/main/java/org/codelibs/core/io/SerializeUtil.java b/src/main/java/org/codelibs/core/io/SerializeUtil.java index 0748c57..844efd1 100644 --- a/src/main/java/org/codelibs/core/io/SerializeUtil.java +++ b/src/main/java/org/codelibs/core/io/SerializeUtil.java @@ -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 DEFAULT_ALLOWED_PATTERNS = Set.of( - "java.lang.*", - "java.util.*", - "java.time.*", - "java.math.*", - "org.codelibs.*", - "[*" // Allow arrays - ); + private static final Set 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. diff --git a/src/main/java/org/codelibs/core/lang/StringUtil.java b/src/main/java/org/codelibs/core/lang/StringUtil.java index 4b86398..91ab09d 100644 --- a/src/main/java/org/codelibs/core/lang/StringUtil.java +++ b/src/main/java/org/codelibs/core/lang/StringUtil.java @@ -50,7 +50,6 @@ protected StringUtil() { */ public static final String[] EMPTY_STRINGS = new String[0]; - /** * Checks if the string is empty or null. * diff --git a/src/main/java/org/codelibs/core/lang/SystemUtil.java b/src/main/java/org/codelibs/core/lang/SystemUtil.java index 4c300e2..89ce26e 100644 --- a/src/main/java/org/codelibs/core/lang/SystemUtil.java +++ b/src/main/java/org/codelibs/core/lang/SystemUtil.java @@ -15,6 +15,8 @@ */ package org.codelibs.core.lang; +import java.util.function.LongSupplier; + /** * Utility class for system operations. * @@ -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; } } diff --git a/src/main/java/org/codelibs/core/log/Logger.java b/src/main/java/org/codelibs/core/log/Logger.java index fe51148..9206ab2 100644 --- a/src/main/java/org/codelibs/core/log/Logger.java +++ b/src/main/java/org/codelibs/core/log/Logger.java @@ -127,19 +127,51 @@ protected static synchronized void initialize() { /** * Returns the logger adapter factory. *

- * 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) *

* * @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 + } + } + + // 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) { + return false; } } diff --git a/src/main/java/org/codelibs/core/misc/DisposableUtil.java b/src/main/java/org/codelibs/core/misc/DisposableUtil.java index 2481221..7eb1e4d 100644 --- a/src/main/java/org/codelibs/core/misc/DisposableUtil.java +++ b/src/main/java/org/codelibs/core/misc/DisposableUtil.java @@ -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. } } disposables.clear(); diff --git a/src/main/java/org/codelibs/core/misc/LocaleUtil.java b/src/main/java/org/codelibs/core/misc/LocaleUtil.java index 5883cab..7dc92db 100644 --- a/src/main/java/org/codelibs/core/misc/LocaleUtil.java +++ b/src/main/java/org/codelibs/core/misc/LocaleUtil.java @@ -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(); + } + + // 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]); + default: + return LocaleUtil.getDefault(); } - return locale; } private static Supplier defaultLocaleSupplier;