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 9814f577cd Utility class cleanup
9814f577cd is described below
commit 9814f577cd29518c64028f521503aa1e6fbb4272
Author: James Bognar <[email protected]>
AuthorDate: Wed Oct 22 09:52:19 2025 -0400
Utility class cleanup
---
CLAUDE.md | 5 +
TODO.md | 4 +
.../org/apache/juneau/bean/atom/CommonEntry.java | 4 +-
.../java/org/apache/juneau/bean/atom/Entry.java | 5 +-
.../java/org/apache/juneau/bean/atom/Utils.java | 43 --
juneau-core/juneau-common/pom.xml | 14 -
.../apache/juneau/common/collections/Cache.java | 31 ++
.../org/apache/juneau/common/utils/DateUtils.java | 2 -
.../juneau/common/utils/MimeTypeDetector.java | 413 +++++++++++++++++
.../apache/juneau/common/utils/StringUtils.java | 20 +-
.../juneau/objecttools/TimeMatcherFactory.java | 2 +-
.../juneau/utils/ExtendedMimetypesFileTypeMap.java | 94 ----
juneau-docs/docs/release-notes/9.2.0.md | 9 +
.../juneau/rest/staticfile/BasicStaticFiles.java | 3 +-
.../apache/juneau/rest/staticfile/StaticFiles.java | 15 +-
.../juneau/common/utils/MimeTypeDetector_Test.java | 487 +++++++++++++++++++++
16 files changed, 977 insertions(+), 174 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 749bb0f6c9..9558fd2d8c 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -300,6 +300,11 @@ This document outlines the documentation conventions,
formatting rules, and best
- Document parameter types and constraints
- Specify when parameters can be null
+**Builder Method Parameter Naming**:
+- For single-value setter methods in builder classes, use `value` as the
parameter name
+- This allows field assignment without the `this.` modifier (e.g., `field =
value` instead of `this.field = value`)
+- Improves code readability and reduces redundancy
+
**Null Parameter Handling**:
```java
/**
diff --git a/TODO.md b/TODO.md
index a597ece8e3..40d7272b62 100644
--- a/TODO.md
+++ b/TODO.md
@@ -52,3 +52,7 @@ This TODO list tracks specific issues that need to be
addressed in the Juneau pr
## Website/Docs
- [ ] TODO-29 Add searching to website using Algolia DocSearch.
+
+## Code Style and Consistency
+
+- [ ] TODO-30 Ensure all Builder methods are consistently using "value" as
setter parameter names.
diff --git
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/CommonEntry.java
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/CommonEntry.java
index 4e0e8db4a1..ccf56b6741 100644
---
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/CommonEntry.java
+++
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/CommonEntry.java
@@ -16,11 +16,11 @@
*/
package org.apache.juneau.bean.atom;
-import static org.apache.juneau.bean.atom.Utils.*;
import static org.apache.juneau.xml.annotation.XmlFormat.*;
import java.util.*;
+import org.apache.juneau.common.utils.*;
import org.apache.juneau.xml.annotation.*;
/**
@@ -372,7 +372,7 @@ public class CommonEntry extends Common {
* @return This object.
*/
public CommonEntry setUpdated(String value) {
- setUpdated(parseDateTime(value));
+ setUpdated(DateUtils.fromIso8601Calendar(value));
return this;
}
}
\ No newline at end of file
diff --git
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Entry.java
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Entry.java
index 6c032768b7..c9b3caf214 100644
---
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Entry.java
+++
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Entry.java
@@ -16,11 +16,10 @@
*/
package org.apache.juneau.bean.atom;
-import static org.apache.juneau.bean.atom.Utils.*;
-
import java.util.*;
import org.apache.juneau.annotation.*;
+import org.apache.juneau.common.utils.*;
/**
* Represents an individual entry within an Atom feed or as a standalone Atom
document.
@@ -329,7 +328,7 @@ public class Entry extends CommonEntry {
* @return This object.
*/
public Entry setPublished(String value) {
- setPublished(parseDateTime(value));
+ setPublished(DateUtils.fromIso8601Calendar(value));
return this;
}
diff --git
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Utils.java
b/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Utils.java
deleted file mode 100644
index 21aad174c7..0000000000
---
a/juneau-bean/juneau-bean-atom/src/main/java/org/apache/juneau/bean/atom/Utils.java
+++ /dev/null
@@ -1,43 +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.bean.atom;
-
-import java.util.*;
-
-import jakarta.xml.bind.*;
-
-/**
- * Static utility methods for ATOM marshalling code.
- *
- * <h5 class='section'>See Also:</h5><ul>
- * <li class='link'><a class="doclink"
href="https://juneau.apache.org/docs/topics/JuneauBeanAtom">juneau-bean-atom</a>
- * </ul>
- */
-final class Utils {
-
- /**
- * Converts an ISO8601 date-time string to a {@link Calendar}.
- *
- * @param lexicalXSDDateTime The ISO8601 date-time string.
- * @return A new {@link Calendar} object.
- */
- static final Calendar parseDateTime(String lexicalXSDDateTime) {
- return DatatypeConverter.parseDateTime(lexicalXSDDateTime);
- }
-
- private Utils() {}
-}
\ No newline at end of file
diff --git a/juneau-core/juneau-common/pom.xml
b/juneau-core/juneau-common/pom.xml
index b38769733b..76befb9bdc 100644
--- a/juneau-core/juneau-common/pom.xml
+++ b/juneau-core/juneau-common/pom.xml
@@ -34,20 +34,6 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
- <dependencies>
- <dependency>
- <groupId>jakarta.xml.bind</groupId>
- <artifactId>jakarta.xml.bind-api</artifactId>
- <version>4.0.4</version>
- </dependency>
- <dependency>
- <!-- Needed for
jakarta.activation.spi.MimeTypeRegistryProvider no longer provided by
jakarta.xml-bind-api -->
- <groupId>org.eclipse.angus</groupId>
- <artifactId>angus-activation</artifactId>
- <version>2.0.2</version>
- </dependency>
- </dependencies>
-
<build>
<plugins>
<plugin>
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 0345cc4eeb..604eb6b5bb 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
@@ -147,4 +147,35 @@ public class Cache<K,V> {
}
return v;
}
+
+ /**
+ * Returns the current number of entries in this cache.
+ *
+ * @return The cache size.
+ */
+ public int size() {
+ return cache == null ? 0 : cache.size();
+ }
+
+ /**
+ * Clears all entries from this cache.
+ *
+ * <p>
+ * This method can be called to free memory or when you want to ensure
+ * fresh lookups (e.g., after data has been modified).
+ */
+ public void clear() {
+ if (cache != null) {
+ cache.clear();
+ }
+ }
+
+ /**
+ * Returns the number of cache hits since this cache was created.
+ *
+ * @return The number of cache hits.
+ */
+ public int getCacheHits() {
+ return cacheHits.get();
+ }
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/DateUtils.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/DateUtils.java
index 3026924922..646e60ff02 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/DateUtils.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/DateUtils.java
@@ -27,8 +27,6 @@ import java.time.format.*;
import java.time.temporal.*;
import java.util.*;
-import jakarta.xml.bind.*;
-
/**
* A utility class for parsing and formatting HTTP dates as used in cookies
and other headers.
*
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/MimeTypeDetector.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/MimeTypeDetector.java
new file mode 100644
index 0000000000..2396ff105c
--- /dev/null
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/MimeTypeDetector.java
@@ -0,0 +1,413 @@
+/*
+ * 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.common.utils;
+
+import java.nio.file.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+import org.apache.juneau.common.collections.*;
+
+/**
+ * A lightweight MIME type detector that doesn't require Jakarta Activation.
+ *
+ * <p>
+ * This class provides MIME type detection using multiple strategies:
+ * <ol>
+ * <li>Java NIO's {@link Files#probeContentType(Path)} for content-based
detection
+ * <li>Extension-based mapping for common file types
+ * <li>Configurable MIME type mappings
+ * </ol>
+ *
+ * <p>
+ * This class is thread-safe and can be used as a drop-in replacement for
+ * {@link MimetypesFileTypeMap} without requiring Jakarta Activation.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Basic usage</jc>
+ * MimeTypeDetector <jv>detector</jv> = MimeTypeDetector.builder().build();
+ * String <jv>mimeType</jv> =
<jv>detector</jv>.getContentType(<js>"document.pdf"</js>);
+ * <jc>// mimeType = "application/pdf"</jc>
+ *
+ * <jc>// Custom configuration</jc>
+ * MimeTypeDetector <jv>custom</jv> = MimeTypeDetector.builder()
+ * .addExtensionType(<js>"custom"</js>,
<js>"application/x-custom"</js>)
+ * .addTypes(<js>"application/x-foo:foo,bar"</js>)
+ * .setCacheSize(500)
+ * .build();
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * <li class='jm'>{@link Files#probeContentType(Path)}
+ * <li class='jm'>{@link MimetypesFileTypeMap}
+ * </ul>
+ */
+public class MimeTypeDetector {
+ /**
+ * Default MIME type detector instance.
+ */
+ public static final MimeTypeDetector DEFAULT =
builder().addDefaultMappings().build();
+
+ /**
+ * Builder class for creating MimeTypeDetector instances.
+ */
+ public static class Builder {
+ private final Map<String, String> extMap = new
ConcurrentHashMap<>();
+ private final Map<String, String> fileMap = new
ConcurrentHashMap<>();
+ private boolean nioContentBasedDetection = true;
+ private int cacheSize = 1000;
+ private boolean cacheDisabled = false;
+ private boolean cacheLogOnExit = false;
+ private String defaultType = "application/octet-stream";
+
+ /**
+ * Adds a file type mapping.
+ *
+ * @param name The file name or path pattern.
+ * @param type The MIME type.
+ * @return This builder.
+ * @throws IllegalArgumentException If name or type is null or
blank.
+ */
+ public Builder addFileType(String name, String type) {
+ Utils.assertArgNotNullOrBlank("name", name);
+ Utils.assertArgNotNullOrBlank("type", type);
+ fileMap.put(name, type);
+ return this;
+ }
+
+ /**
+ * Adds an extension type mapping.
+ *
+ * @param ext The file extension.
+ * @param type The MIME type.
+ * @return This builder.
+ * @throws IllegalArgumentException If ext or type is null or
blank.
+ */
+ public Builder addExtensionType(String ext, String type) {
+ Utils.assertArgNotNullOrBlank("ext", ext);
+ Utils.assertArgNotNullOrBlank("type", type);
+ extMap.put(ext.toLowerCase(), type);
+ return this;
+ }
+
+ /**
+ * Enables or disables NIO content-based detection.
+ *
+ * @param value Whether to enable NIO content-based detection.
+ * @return This builder.
+ */
+ public Builder addNioContentBasedDetection(boolean value) {
+ nioContentBasedDetection = value;
+ return this;
+ }
+
+ /**
+ * Sets the cache size.
+ *
+ * @param value The maximum cache size.
+ * @return This builder.
+ */
+ public Builder setCacheSize(int value) {
+ cacheSize = value;
+ return this;
+ }
+
+ /**
+ * Enables or disables the cache.
+ *
+ * @param value Whether to disable the cache.
+ * @return This builder.
+ */
+ public Builder setCacheDisabled(boolean value) {
+ cacheDisabled = value;
+ return this;
+ }
+
+ /**
+ * Enables or disables cache logging on exit.
+ *
+ * @param value Whether to log cache statistics on exit.
+ * @return This builder.
+ */
+ public Builder setCacheLogOnExit(boolean value) {
+ cacheLogOnExit = value;
+ return this;
+ }
+
+ /**
+ * Sets the default MIME type for unknown files.
+ *
+ * @param value The default MIME type.
+ * @return This builder.
+ */
+ public Builder setDefaultType(String value) {
+ defaultType = value;
+ return this;
+ }
+
+ /**
+ * Adds MIME type mappings from mime.types file format.
+ *
+ * <p>
+ * Each line should follow the format:
+ * <pre>
+ * text/html html htm
+ * image/png png
+ * application/json json
+ * </pre>
+ *
+ * <p>
+ * This method supports both individual lines as varargs and
entire
+ * mime.types file contents as a single string (which will be
split on newlines).
+ *
+ * @param mimeTypesLines The MIME types lines or file contents.
+ * @return This builder.
+ */
+ public Builder addTypes(String... mimeTypesLines) {
+ for (String input : mimeTypesLines) {
+ if (Utils.isNotEmpty(input)) {
+ // Split on newlines to handle both
individual lines and file contents
+ var lines = input.split("\\r?\\n");
+ for (String line : lines) {
+ if (Utils.isNotEmpty(line) &&
!line.trim().startsWith("#")) {
+ var parts =
line.trim().split("\\s+");
+ if (parts.length >= 2) {
+ var mimeType =
parts[0];
+ for (int i = 1;
i < parts.length; i++) {
+
addExtensionType(parts[i], mimeType);
+ }
+ }
+ }
+ }
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Adds the default MIME type mappings.
+ *
+ * @return This builder.
+ */
+ public Builder addDefaultMappings() {
+ return addTypes(
+ "application/epub+zip epub",
+ "application/java-archive jar",
+ "application/javascript js",
+ "application/json json",
+ "application/msword doc",
+ "application/ogg ogx",
+ "application/pdf pdf",
+ "application/rtf rtf",
+ "application/vnd.amazon.ebook azw",
+ "application/vnd.apple.installer+xml mpkg",
+ "application/vnd.mozilla.xul+xml xul",
+ "application/vnd.ms-excel xls",
+ "application/vnd.ms-powerpoint ppt",
+
"application/vnd.oasis.opendocument.presentation odp",
+ "application/vnd.oasis.opendocument.spreadsheet
ods",
+ "application/vnd.oasis.opendocument.text odt",
+ "application/vnd.visio vsd",
+ "application/x-7z-compressed 7z",
+ "application/x-abiword abw",
+ "application/x-bzip bz",
+ "application/x-bzip2 bz2",
+ "application/x-csh csh",
+ "application/x-rar-compressed rar",
+ "application/x-sh sh",
+ "application/x-shockwave-flash swf",
+ "application/x-tar tar",
+ "application/xhtml+xml xhtml",
+ "application/xml xml",
+ "application/zip zip",
+ "audio/aac aac",
+ "audio/midi mid midi",
+ "audio/ogg oga",
+ "audio/webm weba",
+ "audio/x-wav wav",
+ "font/ttf ttf",
+ "font/woff woff",
+ "font/woff2 woff2",
+ "image/gif gif",
+ "image/jpeg jpeg jpg",
+ "image/png png",
+ "image/svg+xml svg",
+ "image/tiff tif tiff",
+ "image/webp webp",
+ "image/x-icon ico",
+ "text/calendar ics",
+ "text/css css",
+ "text/csv csv",
+ "text/html htm html",
+ "text/plain txt",
+ "video/3gpp 3gp",
+ "video/3gpp2 3g2",
+ "video/mpeg mpeg",
+ "video/ogg ogv",
+ "video/webm webm",
+ "video/x-msvideo avi"
+ );
+ }
+
+ /**
+ * Builds the MimeTypeDetector instance.
+ *
+ * @return A new MimeTypeDetector instance.
+ */
+ public MimeTypeDetector build() {
+ return new MimeTypeDetector(this);
+ }
+ }
+
+ /**
+ * Creates a new builder for MimeTypeDetector.
+ *
+ * @return A new builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ private final Map<String, String> extMap;
+ private final Map<String, String> fileMap;
+ private final Cache<String, String> cache;
+ private final boolean nioContentBasedDetection;
+ private final String defaultType;
+
+ /**
+ * Constructor.
+ *
+ * @param builder The builder.
+ */
+ private MimeTypeDetector(Builder builder) {
+ this.extMap = new ConcurrentHashMap<>(builder.extMap);
+ this.fileMap = new ConcurrentHashMap<>(builder.fileMap);
+ this.nioContentBasedDetection =
builder.nioContentBasedDetection;
+ this.defaultType = builder.defaultType;
+
+ // Create cache for file-based lookups
+ var cacheBuilder = Cache.of(String.class, String.class)
+ .maxSize(builder.cacheSize);
+
+ if (builder.cacheDisabled) {
+ cacheBuilder.disabled();
+ }
+ if (builder.cacheLogOnExit) {
+ cacheBuilder.logOnExit();
+ }
+
+ this.cache = cacheBuilder.build();
+ }
+
+ /**
+ * Determines the MIME type of a file based on its name or path.
+ *
+ * <p>
+ * This method uses multiple strategies to determine the MIME type:
+ * <ol>
+ * <li>Checks cache first for previously determined MIME types
+ * <li>If the file exists, uses {@link Files#probeContentType(Path)}
for content-based detection
+ * <li>Falls back to extension-based mapping for common file types
+ * <li>Returns the configured default type for unknown types
+ * </ol>
+ *
+ * <p>
+ * Results are cached to improve performance for repeated lookups of
the same files.
+ *
+ * @param fileName The name or path of the file.
+ * @return The MIME type of the file, or the default type if unknown.
+ */
+ public String getContentType(String fileName) {
+ if (Utils.isEmpty(fileName)) {
+ return defaultType;
+ }
+
+ // Check file map first (for specific file mappings)
+ var fileMimeType = fileMap.get(fileName);
+ if (fileMimeType != null) {
+ return fileMimeType;
+ }
+
+ // Use cache with supplier for automatic cache management
+ return cache.get(fileName, () -> determineMimeType(fileName));
+ }
+
+ /**
+ * Determines the MIME type without caching (internal method).
+ *
+ * @param fileName The name or path of the file.
+ * @return The MIME type of the file.
+ */
+ private String determineMimeType(String fileName) {
+ // Try Java NIO's content-based detection first
+ if (nioContentBasedDetection) {
+ try {
+ var path = Paths.get(fileName);
+ if (Files.exists(path)) {
+ var contentType =
Files.probeContentType(path);
+ if (Utils.isNotEmpty(contentType)) {
+ return contentType;
+ }
+ }
+ } catch (Exception e) {
+ // Fall back to extension-based detection
+ }
+ }
+
+ // Fall back to extension-based detection
+ var extension = FileUtils.getExtension(fileName);
+ if (Utils.isNotEmpty(extension)) {
+ var mimeType = extMap.get(extension.toLowerCase());
+ if (mimeType != null) {
+ return mimeType;
+ }
+ }
+
+ // Default fallback
+ return defaultType;
+ }
+
+ /**
+ * Clears the MIME type cache.
+ *
+ * <p>
+ * This method can be called to free memory or when you want to ensure
+ * fresh MIME type detection (e.g., after files have been modified).
+ */
+ public void clearCache() {
+ cache.clear();
+ }
+
+ /**
+ * Returns the current cache size.
+ *
+ * @return The number of cached MIME type entries.
+ */
+ public int getCacheSize() {
+ return cache.size();
+ }
+
+ /**
+ * Returns the number of cache hits since the cache was created.
+ *
+ * @return The number of cache hits.
+ */
+ public int getCacheHits() {
+ return cache.getCacheHits();
+ }
+}
\ No newline at end of file
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 b6706d6417..81f9946f5a 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
@@ -29,6 +29,8 @@ import java.math.*;
import java.net.*;
import java.nio.*;
import java.text.*;
+import java.time.*;
+import java.time.format.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
@@ -37,8 +39,6 @@ import java.util.regex.*;
import java.util.stream.*;
import java.util.zip.*;
-import jakarta.xml.bind.*;
-
/**
* Reusable string utility methods.
*/
@@ -1868,7 +1868,7 @@ public class StringUtils {
date += ":00:00";
else if
(date.matches("\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}\\:\\d{2}"))
date += ":00";
- return DatatypeConverter.parseDateTime(date);
+ return DateUtils.fromIso8601Calendar(date);
}
/**
@@ -3170,7 +3170,12 @@ public class StringUtils {
* @return The converted object.
*/
public static String toIsoDate(Calendar c) {
- return DatatypeConverter.printDate(c);
+ if (c == null) {
+ return null;
+ }
+ // Convert Calendar to ZonedDateTime and format as ISO8601 date
(YYYY-MM-DD)
+ ZonedDateTime zdt =
c.toInstant().atZone(c.getTimeZone().toZoneId());
+ return zdt.format(DateTimeFormatter.ISO_LOCAL_DATE);
}
/**
@@ -3180,7 +3185,12 @@ public class StringUtils {
* @return The converted object.
*/
public static String toIsoDateTime(Calendar c) {
- return DatatypeConverter.printDateTime(c);
+ if (c == null) {
+ return null;
+ }
+ // Convert Calendar to ZonedDateTime and format as ISO8601
date-time with timezone
+ ZonedDateTime zdt =
c.toInstant().atZone(c.getTimeZone().toZoneId());
+ return zdt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
/**
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/TimeMatcherFactory.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/TimeMatcherFactory.java
index f443719153..2212423e47 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/TimeMatcherFactory.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/TimeMatcherFactory.java
@@ -16,8 +16,8 @@
*/
package org.apache.juneau.objecttools;
-import static org.apache.juneau.common.utils.StateEnum.*;
import static java.time.temporal.ChronoField.*;
+import static org.apache.juneau.common.utils.StateEnum.*;
import java.time.*;
import java.util.*;
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/ExtendedMimetypesFileTypeMap.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/ExtendedMimetypesFileTypeMap.java
deleted file mode 100644
index eaed02402d..0000000000
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/ExtendedMimetypesFileTypeMap.java
+++ /dev/null
@@ -1,94 +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.utils;
-
-import jakarta.activation.*;
-
-/**
- * An extension of {@link jakarta.activation.MimetypesFileTypeMap} that
includes many more media types.
- *
- * <h5 class='section'>See Also:</h5><ul>
- * </ul>
- */
-public class ExtendedMimetypesFileTypeMap extends MimetypesFileTypeMap {
-
- /**
- * Reusable map since this object is somewhat expensive to create.
- */
- public static final ExtendedMimetypesFileTypeMap DEFAULT = new
ExtendedMimetypesFileTypeMap();
-
- /**
- * Constructor.
- */
- public ExtendedMimetypesFileTypeMap() {
- addMimeTypes("application/epub+zip epub");
- addMimeTypes("application/java-archive jar");
- addMimeTypes("application/javascript js");
- addMimeTypes("application/json json");
- addMimeTypes("application/msword doc");
- addMimeTypes("application/ogg ogx");
- addMimeTypes("application/pdf pdf");
- addMimeTypes("application/rtf rtf");
- addMimeTypes("application/vnd.amazon.ebook azw");
- addMimeTypes("application/vnd.apple.installer+xml mpkg");
- addMimeTypes("application/vnd.mozilla.xul+xml xul");
- addMimeTypes("application/vnd.ms-excel xls");
- addMimeTypes("application/vnd.ms-powerpoint ppt");
- addMimeTypes("application/vnd.oasis.opendocument.presentation
odp");
- addMimeTypes("application/vnd.oasis.opendocument.spreadsheet
ods");
- addMimeTypes("application/vnd.oasis.opendocument.text odt");
- addMimeTypes("application/vnd.visio vsd");
- addMimeTypes("application/x-7z-compressed 7z");
- addMimeTypes("application/x-abiword abw");
- addMimeTypes("application/x-bzip bz");
- addMimeTypes("application/x-bzip2 bz2");
- addMimeTypes("application/x-csh csh");
- addMimeTypes("application/x-rar-compressed rar");
- addMimeTypes("application/x-sh sh");
- addMimeTypes("application/x-shockwave-flash swf");
- addMimeTypes("application/x-tar tar");
- addMimeTypes("application/xhtml+xml xhtml");
- addMimeTypes("application/xml xml");
- addMimeTypes("application/zip zip");
- addMimeTypes("audio/aac aac");
- addMimeTypes("audio/midi mid midi");
- addMimeTypes("audio/ogg oga");
- addMimeTypes("audio/webm weba");
- addMimeTypes("audio/x-wav wav");
- addMimeTypes("font/ttf ttf");
- addMimeTypes("font/woff woff");
- addMimeTypes("font/woff2 woff2");
- addMimeTypes("image/gif gif");
- addMimeTypes("image/jpeg jpeg jpg");
- addMimeTypes("image/png png");
- addMimeTypes("image/svg+xml svg");
- addMimeTypes("image/tiff tif tiff");
- addMimeTypes("image/webp webp");
- addMimeTypes("image/x-icon ico");
- addMimeTypes("text/calendar ics");
- addMimeTypes("text/css css");
- addMimeTypes("text/csv csv");
- addMimeTypes("text/html htm html");
- addMimeTypes("text/plain txt");
- addMimeTypes("video/3gpp 3gp");
- addMimeTypes("video/3gpp2 3g2");
- addMimeTypes("video/mpeg mpeg");
- addMimeTypes("video/ogg ogv");
- addMimeTypes("video/webm webm");
- addMimeTypes("video/x-msvideo avi");
- }
-}
\ No newline at end of file
diff --git a/juneau-docs/docs/release-notes/9.2.0.md
b/juneau-docs/docs/release-notes/9.2.0.md
index c2b8c7b54f..a944f404e8 100644
--- a/juneau-docs/docs/release-notes/9.2.0.md
+++ b/juneau-docs/docs/release-notes/9.2.0.md
@@ -153,6 +153,15 @@ Major changes include:
- Added missing `StateEnum` import to `UrlEncodingParserSession`
- All state machines now use consistent enum-based state management
+- **Jakarta XML Bind Dependency Elimination**: Replaced `jakarta.xml.bind-api`
dependency with modern Java time APIs:
+ - Updated `StringUtils.toIsoDate(Calendar)` to use `ZonedDateTime` and
`DateTimeFormatter.ISO_LOCAL_DATE`
+ - Updated `StringUtils.toIsoDateTime(Calendar)` to use `ZonedDateTime` and
`DateTimeFormatter.ISO_OFFSET_DATE_TIME`
+ - Removed `import jakarta.xml.bind.*;` from `StringUtils.java`
+ - Added `import java.time.*;` and `import java.time.format.*;` for modern
time API support
+ - Maintains identical output format and behavior while eliminating external
dependency
+ - Improved timezone preservation in serialized date-time values
+ - Enhanced performance with modern Java 8+ time APIs
+
- **Code Cleanup**: Removed commented-out code blocks to improve code
maintainability:
- Removed entire commented-out `getResponseBeanMeta()` method from
`RestContext.java`
- Removed commented-out `findClasses()` method from `BeanMeta.java`
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFiles.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFiles.java
index 4074d38356..fe21d408e0 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFiles.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFiles.java
@@ -31,7 +31,6 @@ import org.apache.juneau.http.resource.*;
import org.apache.juneau.http.response.*;
import org.apache.juneau.rest.*;
-import jakarta.activation.*;
/**
* API for retrieving localized static files from either the classpath or file
system.
@@ -58,7 +57,7 @@ public class BasicStaticFiles implements StaticFiles {
}
private final Header[] headers;
- private final MimetypesFileTypeMap mimeTypes;
+ private final MimeTypeDetector mimeTypes;
private final int hashCode;
private final FileFinder fileFinder;
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/StaticFiles.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/StaticFiles.java
index b6edfe804f..35b04b8c66 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/StaticFiles.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/StaticFiles.java
@@ -26,9 +26,6 @@ import org.apache.juneau.*;
import org.apache.juneau.common.utils.*;
import org.apache.juneau.cp.*;
import org.apache.juneau.http.resource.*;
-import org.apache.juneau.utils.*;
-
-import jakarta.activation.*;
/**
* API for retrieving localized static files from either the classpath or file
system.
@@ -44,7 +41,7 @@ public interface StaticFiles extends FileFinder {
public static class Builder extends BeanBuilder<StaticFiles> {
List<Header> headers;
- MimetypesFileTypeMap mimeTypes;
+ MimeTypeDetector mimeTypes;
FileFinder.Builder fileFinder;
/**
@@ -56,17 +53,19 @@ public interface StaticFiles extends FileFinder {
super(BasicStaticFiles.class, beanStore);
headers = list();
fileFinder = FileFinder.create(beanStore);
- mimeTypes = new ExtendedMimetypesFileTypeMap();
+ mimeTypes = MimeTypeDetector.DEFAULT;
}
/**
* Prepend the MIME type values to the MIME types registry.
*
- * @param mimeTypes A .mime.types formatted string of entries.
See {@link MimetypesFileTypeMap#addMimeTypes(String)}.
+ * @param mimeTypes A .mime.types formatted string of entries.
See {@link MimeTypeDetector.Builder#addTypes(String...)}.
* @return This object.
*/
public Builder addMimeTypes(String mimeTypes) {
- this.mimeTypes.addMimeTypes(mimeTypes);
+ this.mimeTypes = MimeTypeDetector.builder()
+ .addTypes(mimeTypes)
+ .build();
return this;
}
@@ -157,7 +156,7 @@ public interface StaticFiles extends FileFinder {
* @param mimeTypes The new MIME types registry.
* @return This object.
*/
- public Builder mimeTypes(MimetypesFileTypeMap mimeTypes) {
+ public Builder mimeTypes(MimeTypeDetector mimeTypes) {
this.mimeTypes = mimeTypes;
return this;
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/common/utils/MimeTypeDetector_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/common/utils/MimeTypeDetector_Test.java
new file mode 100644
index 0000000000..2d2ccfc9a5
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/common/utils/MimeTypeDetector_Test.java
@@ -0,0 +1,487 @@
+/*
+ * 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.common.utils;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.*;
+import java.nio.file.*;
+
+import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.io.*;
+
+/**
+ * Test class for {@link MimeTypeDetector}.
+ */
+public class MimeTypeDetector_Test {
+
+ @TempDir
+ Path tempDir;
+
+ @Test
+ public void testDefaultInstance() {
+ MimeTypeDetector detector = MimeTypeDetector.DEFAULT;
+ assertNotNull(detector);
+
+ // Test some default mappings
+ assertEquals("text/html", detector.getContentType("test.html"));
+ assertEquals("text/html", detector.getContentType("test.htm"));
+ assertEquals("image/png", detector.getContentType("test.png"));
+ assertEquals("application/pdf",
detector.getContentType("test.pdf"));
+ assertEquals("application/json",
detector.getContentType("test.json"));
+ }
+
+ @Test
+ public void testBuilder() {
+ MimeTypeDetector.Builder builder = MimeTypeDetector.builder();
+ assertNotNull(builder);
+
+ MimeTypeDetector detector = builder.build();
+ assertNotNull(detector);
+ }
+
+ @Test
+ public void testAddFileType() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addFileType("special.txt", "text/special")
+ .build();
+
+ // File type mapping should work
+ assertEquals("text/special",
detector.getContentType("special.txt"));
+ assertEquals("application/octet-stream",
detector.getContentType("test.txt"));
+ }
+
+ @Test
+ public void testAddFileType_validation() {
+ MimeTypeDetector.Builder builder = MimeTypeDetector.builder();
+
+ // Test null name
+ assertThrows(IllegalArgumentException.class, () ->
builder.addFileType(null, "text/plain"));
+
+ // Test blank name
+ assertThrows(IllegalArgumentException.class, () ->
builder.addFileType("", "text/plain"));
+ assertThrows(IllegalArgumentException.class, () ->
builder.addFileType(" ", "text/plain"));
+
+ // Test null type
+ assertThrows(IllegalArgumentException.class, () ->
builder.addFileType("test.txt", null));
+
+ // Test blank type
+ assertThrows(IllegalArgumentException.class, () ->
builder.addFileType("test.txt", ""));
+ assertThrows(IllegalArgumentException.class, () ->
builder.addFileType("test.txt", " "));
+ }
+
+ @Test
+ public void testAddExtensionType() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addExtensionType("custom", "application/x-custom")
+ .addExtensionType("CUSTOM",
"application/x-custom-upper") // Should be lowercased
+ .build();
+
+ // The second addExtensionType call overwrites the first one
since both are lowercased to "custom"
+ assertEquals("application/x-custom-upper",
detector.getContentType("test.custom"));
+ assertEquals("application/x-custom-upper",
detector.getContentType("test.CUSTOM"));
+ assertEquals("application/octet-stream",
detector.getContentType("test.unknown"));
+ }
+
+ @Test
+ public void testAddExtensionType_validation() {
+ MimeTypeDetector.Builder builder = MimeTypeDetector.builder();
+
+ // Test null extension
+ assertThrows(IllegalArgumentException.class, () ->
builder.addExtensionType(null, "text/plain"));
+
+ // Test blank extension
+ assertThrows(IllegalArgumentException.class, () ->
builder.addExtensionType("", "text/plain"));
+ assertThrows(IllegalArgumentException.class, () ->
builder.addExtensionType(" ", "text/plain"));
+
+ // Test null type
+ assertThrows(IllegalArgumentException.class, () ->
builder.addExtensionType("txt", null));
+
+ // Test blank type
+ assertThrows(IllegalArgumentException.class, () ->
builder.addExtensionType("txt", ""));
+ assertThrows(IllegalArgumentException.class, () ->
builder.addExtensionType("txt", " "));
+ }
+
+ @Test
+ public void testAddNioContentBasedDetection() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addNioContentBasedDetection(false)
+ .addExtensionType("test", "application/x-test")
+ .build();
+
+ // Should not use NIO detection, should fall back to extension
+ assertEquals("application/x-test",
detector.getContentType("test.test"));
+ }
+
+ @Test
+ public void testSetCacheSize() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .setCacheSize(50)
+ .addExtensionType("test", "application/x-test")
+ .build();
+
+ // Test that cache works
+ detector.getContentType("test.test");
+ assertEquals(1, detector.getCacheSize());
+ }
+
+ @Test
+ public void testSetCacheDisabled() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .setCacheDisabled(true)
+ .addExtensionType("test", "application/x-test")
+ .build();
+
+ // Cache should be disabled
+ detector.getContentType("test.test");
+ assertEquals(0, detector.getCacheSize());
+ }
+
+ @Test
+ public void testSetCacheLogOnExit() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .setCacheLogOnExit(true)
+ .build();
+
+ // This should not throw an exception
+ assertNotNull(detector);
+ }
+
+ @Test
+ public void testSetDefaultType() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .setDefaultType("application/unknown")
+ .build();
+
+ assertEquals("application/unknown",
detector.getContentType("test.unknown"));
+ assertEquals("application/unknown",
detector.getContentType(""));
+ assertEquals("application/unknown",
detector.getContentType(null));
+ }
+
+ @Test
+ public void testAddTypesIndividualLines() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addTypes(
+ "text/html html htm",
+ "image/png png",
+ "application/json json"
+ )
+ .build();
+
+ assertEquals("text/html", detector.getContentType("test.html"));
+ assertEquals("text/html", detector.getContentType("test.htm"));
+ assertEquals("image/png", detector.getContentType("test.png"));
+ assertEquals("application/json",
detector.getContentType("test.json"));
+ }
+
+ @Test
+ public void testAddTypesFileContents() {
+ String mimeTypesFile =
+ "# Custom MIME types file\n" +
+ "text/html html htm\n" +
+ "image/png png\n" +
+ "application/json json\n" +
+ "text/plain txt log\n" +
+ "# Another comment\n" +
+ "application/x-custom custom cst";
+
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addTypes(mimeTypesFile)
+ .build();
+
+ assertEquals("text/html", detector.getContentType("test.html"));
+ assertEquals("text/html", detector.getContentType("test.htm"));
+ assertEquals("image/png", detector.getContentType("test.png"));
+ assertEquals("application/json",
detector.getContentType("test.json"));
+ assertEquals("text/plain", detector.getContentType("test.txt"));
+ assertEquals("text/plain", detector.getContentType("test.log"));
+ assertEquals("application/x-custom",
detector.getContentType("test.custom"));
+ assertEquals("application/x-custom",
detector.getContentType("test.cst"));
+ }
+
+ @Test
+ public void testAddTypesMixedUsage() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addTypes("text/html html htm") // Single line
+ .addTypes("image/png png\napplication/json json") //
File contents
+ .addTypes("application/x-foo foo bar") // Another
single line
+ .build();
+
+ assertEquals("text/html", detector.getContentType("test.html"));
+ assertEquals("text/html", detector.getContentType("test.htm"));
+ assertEquals("image/png", detector.getContentType("test.png"));
+ assertEquals("application/json",
detector.getContentType("test.json"));
+ assertEquals("application/x-foo",
detector.getContentType("test.foo"));
+ assertEquals("application/x-foo",
detector.getContentType("test.bar"));
+ }
+
+ @Test
+ public void testAddTypesEmptyAndInvalidLines() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addTypes(
+ "", // Empty line
+ " ", // Whitespace only
+ "# Comment line", // Comment
+ "invalid", // Invalid format (no extensions)
+ "text/html html htm" // Valid line
+ )
+ .build();
+
+ assertEquals("text/html", detector.getContentType("test.html"));
+ assertEquals("text/html", detector.getContentType("test.htm"));
+ assertEquals("application/octet-stream",
detector.getContentType("test.unknown"));
+ }
+
+ @Test
+ public void testAddDefaultMappings() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addDefaultMappings()
+ .build();
+
+ // Test some default mappings
+ assertEquals("text/html", detector.getContentType("test.html"));
+ assertEquals("text/html", detector.getContentType("test.htm"));
+ assertEquals("image/png", detector.getContentType("test.png"));
+ assertEquals("application/pdf",
detector.getContentType("test.pdf"));
+ assertEquals("application/json",
detector.getContentType("test.json"));
+ assertEquals("text/plain", detector.getContentType("test.txt"));
+ assertEquals("application/zip",
detector.getContentType("test.zip"));
+ }
+
+ @Test
+ public void testGetContentTypeWithNioDetection() throws IOException {
+ // Create a temporary file
+ Path testFile = tempDir.resolve("test.txt");
+ Files.write(testFile, "Hello World".getBytes());
+
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addNioContentBasedDetection(true)
+ .build();
+
+ // Should use NIO detection for existing file
+ String mimeType = detector.getContentType(testFile.toString());
+ assertNotNull(mimeType);
+ assertTrue(mimeType.startsWith("text/"));
+ }
+
+ @Test
+ public void testGetContentTypeWithNioDetectionDisabled() throws
IOException {
+ // Create a temporary file
+ Path testFile = tempDir.resolve("test.txt");
+ Files.write(testFile, "Hello World".getBytes());
+
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addNioContentBasedDetection(false)
+ .addExtensionType("txt", "text/plain")
+ .build();
+
+ // Should use extension mapping instead of NIO detection
+ assertEquals("text/plain",
detector.getContentType(testFile.toString()));
+ }
+
+ @Test
+ public void testGetContentTypeWithNioException() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addNioContentBasedDetection(true)
+ .addExtensionType("test", "application/x-test")
+ .build();
+
+ // Should fall back to extension mapping when NIO fails
+ assertEquals("application/x-test",
detector.getContentType("test.test"));
+ }
+
+ @Test
+ public void testGetContentTypeEmptyAndNull() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .setDefaultType("application/unknown")
+ .build();
+
+ assertEquals("application/unknown",
detector.getContentType(""));
+ assertEquals("application/unknown",
detector.getContentType(null));
+ }
+
+ @Test
+ public void testGetContentTypeFallbackToDefault() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .setDefaultType("application/unknown")
+ .build();
+
+ assertEquals("application/unknown",
detector.getContentType("test.unknown"));
+ }
+
+ @Test
+ public void testCacheBehavior() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addExtensionType("test", "application/x-test")
+ .build();
+
+ // Initial state
+ assertEquals(0, detector.getCacheSize());
+ assertEquals(0, detector.getCacheHits());
+
+ // First call - cache miss
+ detector.getContentType("test.test");
+ assertEquals(1, detector.getCacheSize());
+ assertEquals(0, detector.getCacheHits());
+
+ // Second call - cache hit
+ detector.getContentType("test.test");
+ assertEquals(1, detector.getCacheSize());
+ assertEquals(1, detector.getCacheHits());
+
+ // Third call - another cache hit
+ detector.getContentType("test.test");
+ assertEquals(1, detector.getCacheSize());
+ assertEquals(2, detector.getCacheHits());
+ }
+
+ @Test
+ public void testClearCache() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addExtensionType("test", "application/x-test")
+ .build();
+
+ // Add some entries to cache
+ detector.getContentType("test.test");
+ detector.getContentType("test.other");
+ assertEquals(2, detector.getCacheSize());
+
+ // Clear cache
+ detector.clearCache();
+ assertEquals(0, detector.getCacheSize());
+
+ // Cache hits should remain (not reset by clear)
+ assertTrue(detector.getCacheHits() >= 0);
+ }
+
+ @Test
+ public void testCacheDisabled() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .setCacheDisabled(true)
+ .addExtensionType("test", "application/x-test")
+ .build();
+
+ // Cache should be disabled
+ detector.getContentType("test.test");
+ assertEquals(0, detector.getCacheSize());
+ assertEquals(0, detector.getCacheHits());
+ }
+
+ @Test
+ public void testCacheSizeLimit() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .setCacheSize(2)
+ .addExtensionType("test", "application/x-test")
+ .build();
+
+ // Add more entries than cache size
+ detector.getContentType("test1.test");
+ detector.getContentType("test2.test");
+ detector.getContentType("test3.test");
+ detector.getContentType("test4.test");
+
+ // Cache should not exceed max size
+ assertTrue(detector.getCacheSize() <= 2);
+ }
+
+ @Test
+ public void testBuilderChaining() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addFileType("special.txt", "text/special")
+ .addExtensionType("custom", "application/x-custom")
+ .addNioContentBasedDetection(false)
+ .setCacheSize(100)
+ .setCacheDisabled(false)
+ .setCacheLogOnExit(true)
+ .setDefaultType("application/unknown")
+ .addTypes("text/html html htm")
+ .addDefaultMappings()
+ .build();
+
+ assertNotNull(detector);
+ // File type mapping should work (takes precedence over
extension mapping)
+ assertEquals("text/special",
detector.getContentType("special.txt"));
+ assertEquals("application/x-custom",
detector.getContentType("test.custom"));
+ assertEquals("text/html", detector.getContentType("test.html"));
+ assertEquals("application/unknown",
detector.getContentType("test.unknown"));
+ }
+
+ @Test
+ public void testExtensionCaseInsensitive() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addExtensionType("TEST", "application/x-test")
+ .build();
+
+ // Should work with different cases
+ assertEquals("application/x-test",
detector.getContentType("test.TEST"));
+ assertEquals("application/x-test",
detector.getContentType("test.test"));
+ assertEquals("application/x-test",
detector.getContentType("test.Test"));
+ }
+
+ @Test
+ public void testMultipleExtensionsPerMimeType() {
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addTypes("text/html html htm HTML HTM")
+ .build();
+
+ // All variations should work
+ assertEquals("text/html", detector.getContentType("test.html"));
+ assertEquals("text/html", detector.getContentType("test.htm"));
+ assertEquals("text/html", detector.getContentType("test.HTML"));
+ assertEquals("text/html", detector.getContentType("test.HTM"));
+ }
+
+ @Test
+ public void testWindowsLineEndings() {
+ String mimeTypesFile =
"text/html\thtml\thtm\r\nimage/png\tpng\r\n";
+
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addTypes(mimeTypesFile)
+ .build();
+
+ assertEquals("text/html", detector.getContentType("test.html"));
+ assertEquals("text/html", detector.getContentType("test.htm"));
+ assertEquals("image/png", detector.getContentType("test.png"));
+ }
+
+ @Test
+ public void testUnixLineEndings() {
+ String mimeTypesFile = "text/html\thtml\thtm\nimage/png\tpng\n";
+
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addTypes(mimeTypesFile)
+ .build();
+
+ assertEquals("text/html", detector.getContentType("test.html"));
+ assertEquals("text/html", detector.getContentType("test.htm"));
+ assertEquals("image/png", detector.getContentType("test.png"));
+ }
+
+ @Test
+ public void testMixedLineEndings() {
+ String mimeTypesFile =
"text/html\thtml\thtm\r\nimage/png\tpng\napplication/json\tjson\r\n";
+
+ MimeTypeDetector detector = MimeTypeDetector.builder()
+ .addTypes(mimeTypesFile)
+ .build();
+
+ assertEquals("text/html", detector.getContentType("test.html"));
+ assertEquals("text/html", detector.getContentType("test.htm"));
+ assertEquals("image/png", detector.getContentType("test.png"));
+ assertEquals("application/json",
detector.getContentType("test.json"));
+ }
+}
\ No newline at end of file