diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 000000000..1bfce9588
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,326 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## 🚨 CRITICAL RULES - READ FIRST 🚨
+
+**BEFORE doing ANYTHING else, understand these NON-NEGOTIABLE requirements:**
+
+### MANDATORY FULL TEST SUITE VALIDATION
+
+**EVERY change, no matter how small, MUST be followed by running the full test suite:**
+
+```bash
+mvn clean test
+```
+
+**ALL 10,000+ tests MUST pass before:**
+- Moving to the next issue/file/task
+- Committing any changes
+- Asking for human approval
+- Starting any new work
+
+**If even ONE test fails:**
+- Stop immediately
+- Fix the failing test(s)
+- Run the full test suite again
+- Only proceed when ALL tests pass
+
+**This rule applies to ANY code modification and is MORE IMPORTANT than the actual change itself.**
+
+### MANDATORY HUMAN APPROVAL FOR COMMITS
+
+**NEVER commit without explicit "Y" or "Yes" approval from human.**
+
+### MANDATORY HUMAN APPROVAL FOR DEPLOYMENT
+
+**NEVER deploy without explicit human approval. Always ask for permission before starting any deployment process.**
+
+## 🎯 WORK PHILOSOPHY - INCREMENTAL ATOMIC CHANGES 🎯
+
+**Mental Model: Work with a "List of Changes" approach**
+
+### The Change Hierarchy
+- **Top-level changes** (e.g., "Fix security issues in DateUtilities")
+ - **Sub-changes** (e.g., "Fix ReDoS vulnerability", "Fix thread safety")
+ - **Sub-sub-changes** (e.g., "Limit regex repetition", "Add validation tests")
+
+### Workflow for EACH Individual Change
+1. **Pick ONE change** from any level (top-level, sub-change, sub-sub-change)
+2. **Implement the change**
+ - During development: Use single test execution for speed (`mvn test -Dtest=SpecificTest`)
+ - Iterate until the specific functionality works
+3. **When you think the change is complete:**
+ - **MANDATORY**: Run full test suite: `mvn clean test`
+ - **ALL 10,000+ tests MUST pass**
+ - **If ANY test fails**: Fix immediately, run full tests again
+4. **Once ALL tests pass:**
+ - Ask for commit approval: "Should I commit this change? (Y/N)"
+ - Human approves, commit immediately
+ - Move to next change in the list
+
+### Core Principles
+- **Start work**: At the start of new work, create a "Todo" list.
+- **Chat First**: As a general work guideline, when starting a new Todo list, or a feature idea, always "chat first, get agreement from human, then code."
+- **Minimize Work-in-Process**: Keep delta between local files and committed git files as small as possible
+- **Always Healthy State**: Committed code is always in perfect health (all tests pass)
+- **Atomic Commits**: Each commit represents one complete, tested, working change
+- **Human Controls Push**: Human decides when to push commits to remote
+
+**🎯 GOAL: Each change is complete, tested, and committed before starting the next change**
+
+## ADDITIONAL TESTING REQUIREMENTS
+
+**CRITICAL BUILD REQUIREMENT**: The full maven test suite MUST run all 10,000+ tests. If you see only ~10,000 tests, there is an OSGi or JPMS bundle issue that MUST be fixed before continuing any work. Use `mvn -Dbundle.skip=true test` to bypass bundle issues during development, but the underlying bundle configuration must be resolved.
+
+**CRITICAL TESTING REQUIREMENT**: When adding ANY new code (security fixes, new methods, validation logic, etc.), you MUST add corresponding JUnit tests to prove the changes work correctly. This includes:
+- Testing the new functionality works as expected
+- Testing edge cases and error conditions
+- Testing security boundary conditions
+- Testing that the fix actually prevents the vulnerability
+- All new tests MUST pass along with the existing 10,000+ tests
+## Build Commands
+
+**Maven-based Java project with JDK 8 compatibility**
+
+- **Build**: `mvn compile`
+- **Test**: `mvn test`
+- **Package**: `mvn package`
+- **Install**: `mvn install`
+- **Run single test**: `mvn test -Dtest=ClassName`
+- **Run tests with pattern**: `mvn test -Dtest="*Pattern*"`
+- **Clean**: `mvn clean`
+- **Generate docs**: `mvn javadoc:javadoc`
+
+## Architecture Overview
+
+**java-util** is a high-performance Java utilities library focused on memory efficiency, thread-safety, and enhanced collections. The architecture follows these key patterns:
+
+### Core Structure
+- **Main package**: `com.cedarsoftware.util` - Core utilities and enhanced collections
+- **Convert package**: `com.cedarsoftware.util.convert` - Comprehensive type conversion system
+- **Cache package**: `com.cedarsoftware.util.cache` - Caching strategies and implementations
+
+### Key Architectural Patterns
+
+**Memory-Efficient Collections**: CompactMap/CompactSet dynamically adapt storage structure based on size, using arrays for small collections and switching to hash-based storage as they grow.
+
+**Null-Safe Concurrent Collections**: ConcurrentHashMapNullSafe, ConcurrentNavigableMapNullSafe, etc. extend JDK concurrent collections to safely handle null keys/values.
+
+**Dynamic Code Generation**: CompactMap/CompactSet use JDK compiler at runtime to generate optimized subclasses when builder API is used (requires full JDK).
+
+**Converter Architecture**: Modular conversion system with dedicated conversion classes for each target type, supporting thousands of built-in conversions between Java types.
+
+**ClassValue Optimization**: ClassValueMap/ClassValueSet leverage JVM's ClassValue for extremely fast Class-based lookups.
+
+## Development Conventions
+
+### Code Style (from agents.md)
+- Use **four spaces** for indentation—no tabs
+- Keep lines under **120 characters**
+- End files with newline, use Unix line endings
+- Follow standard Javadoc for public APIs
+- **JDK 1.8 source compatibility** - do not use newer language features
+
+### Library Usage Patterns
+- Use `ReflectionUtils` APIs instead of direct reflection
+- Use `DeepEquals.deepEquals()` for data structure verification in tests (pass options to see diff)
+- Use null-safe ConcurrentMaps from java-util for null support
+- Use `DateUtilities.parse()` or `Converter.convert()` for date parsing
+- Use `Converter.convert()` for type marshaling
+- Use `FastByteArrayInputStream/OutputStream` and `FastReader/FastWriter` for performance
+- Use `StringUtilities` APIs for null-safe string operations
+- Use `UniqueIdGenerator.getUniqueId19()` for unique IDs (up to 10,000/ms, strictly increasing)
+- Use `IOUtilities` for stream handling and transfers
+- Use `ClassValueMap/ClassValueSet` for fast Class-based lookups
+- Use `CaseInsensitiveMap` for case-insensitive string keys
+- Use `CompactMap/CompactSet` for memory-efficient large collections
+
+## Testing Framework
+
+- **JUnit 5** (Jupiter) with parameterized tests
+- **AssertJ** for fluent assertions
+- **Mockito** for mocking
+- Test resources in `src/test/resources/`
+- Comprehensive test coverage with pattern: `*Test.java`
+
+## Special Considerations
+
+### JDK vs JRE Environments
+- Builder APIs (`CompactMap.builder()`, `CompactSet.builder()`) require full JDK (compiler tools)
+- These APIs throw `IllegalStateException` in JRE-only environments
+- Use pre-built classes (`CompactLinkedMap`, `CompactCIHashMap`, etc.) or custom subclasses in JRE environments
+
+### OSGi and JPMS Support
+- Full OSGi bundle with proper manifest entries
+- JPMS module `com.cedarsoftware.util` with exports for main packages
+- No runtime dependencies on external libraries
+
+### Thread Safety
+- Many collections are thread-safe by design (Concurrent* classes)
+- LRUCache and TTLCache are thread-safe with configurable strategies
+- Use appropriate concurrent collections for multi-threaded scenarios
+
+## Enhanced Review Loop
+
+**This workflow follows the INCREMENTAL ATOMIC CHANGES philosophy for systematic code reviews and improvements:**
+
+### Step 1: Build Change List (Analysis Phase)
+- Review Java source files using appropriate analysis framework
+- For **Security**: Prioritize by risk (network utilities, reflection, file I/O, crypto, system calls)
+- For **Performance**: Focus on hot paths, collection usage, algorithm efficiency
+- For **Features**: Target specific functionality or API enhancements
+- **Create hierarchical todo list:**
+ - Top-level items (e.g., "Security review of DateUtilities")
+ - Sub-items (e.g., "Fix ReDoS vulnerability", "Fix thread safety")
+ - Sub-sub-items (e.g., "Limit regex repetition", "Add test coverage")
+
+### Step 2: Pick ONE Change from the List
+- Select the highest priority change from ANY level (top, sub, sub-sub)
+- Mark as "in_progress" in todo list
+- **Focus on this ONE change only**
+
+### Step 3: Implement the Single Change
+- Make targeted improvement to address the ONE selected issue
+- **During development iterations**: Use targeted test execution for speed (`mvn test -Dtest=SpecificTest`)
+ - This allows quick feedback loops while developing the specific feature/fix
+ - Continue iterating until the targeted tests pass and functionality works
+- **MANDATORY**: Add comprehensive JUnit tests for this specific change:
+ - Tests that verify the improvement works correctly
+ - Tests for edge cases and boundary conditions
+ - Tests for error handling and regression prevention
+- Follow coding best practices and maintain API compatibility
+- Update Javadoc and comments where appropriate
+
+### Step 4: Completion Gate - ABSOLUTELY MANDATORY
+**When you believe the issue/fix is complete and targeted tests are passing:**
+
+- **Run FULL test suite**: `mvn test` (ALL 10,000+ tests must pass)
+- **If any test fails**: Fix issues immediately, run full tests again
+- **NEVER proceed until ALL tests pass**
+- Mark improvement todos as "completed" only when ALL tests pass
+
+**Development Process:**
+1. **Development Phase**: Use targeted tests (`mvn test -Dtest=SpecificTest`) for fast iteration
+2. **Completion Gate**: Run full test suite (`mvn test`) when you think you're done
+3. **Quality Verification**: ALL 10,000+ tests must pass before proceeding
+
+### Step 5: Update Documentation (for this ONE change)
+- **changelog.md**: Add entry for this specific change under appropriate version
+- **userguide.md**: Update if this change affects public APIs or usage patterns
+- **Javadoc**: Ensure documentation reflects this change
+- **README.md**: Update if this change affects high-level functionality
+
+### Step 6: Request Atomic Commit Approval
+**MANDATORY HUMAN APPROVAL STEP for this ONE change:**
+Present a commit approval request to the human with:
+- Summary of this ONE improvement made (specific security fix, performance enhancement, etc.)
+- List of files modified for this change
+- Test results confirmation (ALL 10,000+ tests passing)
+- Documentation updates made for this change
+- Clear description of this change and its benefits
+- Ask: "Should I commit this change?"
+
+### Step 7: Atomic Commit (Only After Human Approval)
+- **Immediately commit this ONE change** after receiving "Y" approval
+- Use descriptive commit message format for this specific change:
+ ```
+ [Type]: [Brief description of this ONE change]
+
+ - [This specific change implemented]
+ - [Test coverage added for this change]
+ - [Any documentation updated]
+
+ 🤖 Generated with [Claude Code](https://claude.ai/code)
+
+ Co-Authored-By: Claude
+
+
+
+
+## Integration and Module Support
+
+### JPMS (Java Platform Module System)
+
+This library is fully compatible with JPMS, commonly known as Java Modules. It includes a `module-info.class` file that
+specifies module dependencies and exports.
+
+### OSGi
+
+This library also supports OSGi environments. It comes with pre-configured OSGi metadata in the `MANIFEST.MF` file, ensuring easy integration into any OSGi-based application.
+
+### Using in an OSGi Runtime
+
+The jar already ships with all necessary OSGi headers and a `module-info.class`. No `Import-Package` entries for `java.*` packages are required when consuming the bundle.
+
+To add the bundle to an Eclipse feature or any OSGi runtime simply reference it:
+
+```xml
+
+
+Component
+Description
+
+
+Sets
+
+
+
+CompactSet
+Memory-efficient Set that dynamically adapts its storage structure based on size.
+
+
+CaseInsensitiveSet
+Set implementation with case-insensitive
+String handling.
+
+ConcurrentSet
+Thread-safe Set supporting null elements.
+
+
+ConcurrentNavigableSetNullSafe
+Thread-safe
+NavigableSet supporting null elements.
+
+IdentitySet
+High-performance Set using object identity (
+==) instead of equals(). Faster than Collections.newSetFromMap(new IdentityHashMap<>()).
+
+ClassValueSet
+High-performance Set optimized for fast
+Class membership testing using JVM-optimized ClassValue.
+
+IntervalSet
+Thread-safe interval set with O(log n) performance, automatically merges intervals, smart boundary handling for 20+ types, and you can add your own.
+
+
+Maps
+
+
+
+CompactMap
+Memory-efficient Map that dynamically adapts its storage structure based on size.
+
+
+CaseInsensitiveMap
+A
+Map wrapper that provides case-insensitive, case-retentive keys and inherits the features of the wrapped map (e.g., thread-safety from ConcurrentMap types, multi-key support from MultiKeyMap, sorted, thread-safe, allow nulls from ConcurrentNavigableMapNullSafe).
+
+LRUCache
+Thread-safe Least Recently Used cache with configurable eviction strategies.
+
+
+TTLCache
+Thread-safe Time-To-Live cache with optional size limits.
+
+
+TrackingMap
+A
+Map wrapper that tracks key access. Inherits features from wrapped Map, including thread-safety (ConcurrentMap types), sorted, thread-safe, with null support (ConcurrentNavigableMapNullSafe)
+
+ConcurrentHashMapNullSafe
+Thread-safe
+HashMap supporting null keys and values.
+
+ConcurrentNavigableMapNullSafe
+Thread-safe
+NavigableMap supporting null keys and values.
+
+ClassValueMap
+High-performance Map optimized for fast
+Class key lookups using JVM-optimized ClassValue.
+
+MultiKeyMap
+Concurrent map supporting multiple keys.
+
+
+Lists
+
+
+
+ConcurrentList
+High-performance bucket-based concurrent
+List and Deque with lock-free operations.
+
+Utilities
+
+
+
+ArrayUtilities
+Comprehensive array manipulation operations.
+
+
+ByteUtilities
+Byte array and hexadecimal conversion utilities.
+
+
+ClassUtilities
+Class relationship and reflection helper methods.
+
+
+Converter
+An extensive and extensible conversion utility with thousands of built-in transformations between common JDK types (Dates, Collections, Primitives, EnumSets, etc.).
+
+
+DateUtilities
+Advanced date parsing and manipulation.
+
+
+DataGeneratorInputStream
+Memory-efficient
+InputStream for generating test data on-the-fly with multiple generation modes (random, sequential, patterns, custom).
+
+DeepEquals
+Recursive object graph comparison.
+
+
+EncryptionUtilities
+Simplified encryption and checksum operations.
+
+
+Executor
+Streamlined system command execution.
+
+
+GraphComparator
+Object graph difference detection and synchronization.
+
+
+IOUtilities
+Enhanced I/O operations and streaming utilities.
+
+
+MathUtilities
+Extended mathematical operations.
+
+
+ReflectionUtils
+Optimized reflection operations.
+
+
+RegexUtilities
+Safe regex operations with ReDoS protection, pattern caching, and timeout enforcement.
+
+
+StringUtilities
+Extended
+String manipulation operations.
+
+SystemUtilities
+System and environment interaction utilities.
+
+
+Traverser
+Configurable object graph traversal.
+
+
+TypeUtilities
+Advanced Java type introspection and generic resolution utilities.
+
+
+UniqueIdGenerator
+Distributed-safe unique identifier generation.
+
-**Intellij IDEA**
-
-Including in java-util:
-* **ArrayUtilities** - Useful utilities for working with Java's arrays [ ]
-* **ByteUtilities** - Useful routines for converting byte[] to HEX character [] and visa-versa.
-* **CaseInsensitiveMap** - When Strings are used as keys, they are compared without case. Can be used as regular Map with any Java object as keys, just specially handles Strings.
-* **CaseInsensitiveSet** - Set implementation that ignores String case for contains() calls, yet can have any object added to it (does not limit you to adding only Strings to it).
-* **Converter** - Convert from once instance to another. For example, convert("45.3", BigDecimal.class) will convert the String to a BigDecimal. Works for all primitives, primitive wrappers, Date, java.sql.Date, String, BigDecimal, BigInteger, AtomicBoolean, AtomicLong, etc. The method is very generous on what it allows to be converted. For example, a Calendar instance can be input for a Date or Long. Examine source to see all possibilities.
-* **DateUtilities** - Robust date String parser that handles date/time, date, time, time/date, string name months or numeric months, skips comma, etc. English month names only (plus common month name abbreviations), time with/without seconds or milliseconds, y/m/d and m/d/y ordering as well.
-* **DeepEquals** - Compare two object graphs and return 'true' if they are equivalent, 'false' otherwise. This will handle cycles in the graph, and will call an equals() method on an object if it has one, otherwise it will do a field-by-field equivalency check for non-transient fields.
-* **EncryptionUtilities** - Makes it easy to compute MD5, SHA-1, SHA-256, SHA-512 checksums for Strings, byte[], as well as making it easy to AES-128 encrypt Strings and byte[]'s.
-* **Executor** - One line call to execute operating system commands. Executor executor = new Executor(); executor.exec('ls -l'); Call executor.getOut() to fetch the output, executor.getError() retrieve error output. If a -1 is returned, there was an error.
-* **GraphComparator** - Compare any two Java object graphs. It generates a `List` of `Delta` objects which describe the difference between the two Object graphs. This Delta list can be played back, such that `List deltas = GraphComparator.delta(source, target); GraphComparator.applyDelta(source, deltas)` will bring source up to match target. See JUnit test cases for example usage. This is a completely thorough graph difference (and apply delta), including support for `Array`, `Collection`, `Map`, and object field differences.
-* **IOUtilities** - Handy methods for simplifying I/O including such niceties as properly setting up the input stream for HttpUrlConnections based on their specified encoding. Single line .close() method that handles exceptions for you.
-* **MathUtilities** - Handy mathematical algorithms to make your code smaller. For example, minimum of array of values.
-* **ReflectionUtils** - Simple one-liners for many common reflection tasks.
-* **SafeSimpleDateFormat** - Instances of this class can be stored as member variables and reused without any worry about thread safety. Fixing the problems with the JDK's SimpleDateFormat and thread safety (no reentrancy support).
-* **StringUtilities** - Helpful methods that make simple work of common String related tasks.
-* **SystemUtilities** - A Helpful utility methods for working with external entities like the OS, environment variables, and system properties.
-* **TrackingMap** - Map class that tracks when the keys are accessed via .get() or .containsKey(). Provided by @seankellner
-* **Traverser** - Pass any Java object to this Utility class, it will call your passed in anonymous method for each object it encounters while traversing the complete graph. It handles cycles within the graph. Permits you to perform generalized actions on all objects within an object graph.
-* **UniqueIdGenerator** - Generates a Java long unique id, that is unique across up to 100 servers in a cluster, never hands out the same value, has massive entropy, and runs very quickly.
-* **UrlUtitilies** - Fetch cookies from headers, getUrlConnections(), HTTP Response error handler, and more.
-* **UrlInvocationHandler** - Use to easily communicate with RESTful JSON servers, especially ones that implement a Java interface that you have access to.
+### 🚀 Framework Integration Examples
-See [changelog.md](/changelog.md) for revision history.
+For comprehensive framework integration examples including Spring, Jakarta EE, Spring Boot Auto-Configuration, Microservices, Testing, and Performance Monitoring.
+
+Key integrations include:
+- **[Spring Framework](frameworks.md#spring-framework-integration)** - Configuration beans and case-insensitive property handling
+- **[Jakarta EE/JEE](frameworks.md#jakarta-ee--jee-integration)** - CDI producers and validation services
+- **[Spring Boot](frameworks.md#spring-boot-auto-configuration)** - Auto-configuration with corrected cache constructors
+- **[Microservices](frameworks.md#microservices--cloud-native)** - Service discovery and cloud-native configuration
+- **[Testing](frameworks.md#testing-integration)** - Enhanced test comparisons with DeepEquals
+- **[Monitoring](frameworks.md#performance-monitoring-integration)** - Micrometer metrics integration
+
+## Feature Options
-By: John DeRegnaucourt and Ken Partlow
+java-util provides **70+ runtime configuration options** via system properties, enabling:
+
+- **Zero-downtime security hardening** - Enable security features without code changes
+- **Environment-specific tuning** - Different limits for development vs. production
+- **Gradual rollout strategies** - Test new security features with feature flags
+- **Compliance flexibility** - Meet varying regulatory requirements across deployments
+
+All security features are **disabled by default** for backward compatibility.
+
+📖 **[View complete feature options reference →](features.md)**
+
+### Logging
+
+Because `java-util` has no dependencies on other libraries, `java-util` uses the Java built-in `java.util.logging` for all output. See the
+[user guide](userguide.md#redirecting-javautillogging) for ways to route
+these logs to SLF4J or Log4j 2.
+
+### User Guide
+[View detailed documentation on all utilities.](userguide.md)
+
+See [changelog.md](/changelog.md) for revision history.
diff --git a/agents.md b/agents.md
new file mode 100644
index 000000000..08323b2db
--- /dev/null
+++ b/agents.md
@@ -0,0 +1,43 @@
+# AGENTS
+
+These instructions guide any automated agent (such as Codex) that modifies this
+repository.
+
+## Coding Conventions
+- Use **four spaces** for indentation—no tabs.
+- End every file with a newline and use Unix line endings.
+- Keep code lines under **120 characters** where possible.
+- Follow standard Javadoc style for any new public APIs.
+- This library maintains JDK 1.8 source compatibility, please make sure to not use source constructs or expected JDK libary calls beyond JDK 1.8.
+- Whenever you need to use reflection, make sure you use ReflectionUtils APIs from java-util.
+- For data structure verification in JUnit tests, use DeepEquals.deepEquals() [make sure to pass the option so you can see the "diff"]. This will make it clear where there is a difference in a complex data structure.
+- If you need null support in ConcurrentMap implementations, use java-utils ConcurrentMaps that are null safe.
+- Whenever parsing a String date, use either java-util DateUtilities.parse() (Date or ZonedDateTime), or use Converter.converter() which will use it inside.
+- Use Converter.convert() as needed to marshal data types to match.
+- For faster stream reading, use the FastByteArrayInputStream and FastByteArrayOutputStream.
+- For faster Readers, use FastReader and FastWriter.
+- USe StringUtilities APIs for common simplifications like comparing without worrying about null, for example. Many other APIs on there.
+- When a Unique ID is needed, use the UniqueIdGenerator.getUniqueId19() as it will give you a long, up to 10,000 per millisecond, and you can always get the time of when it was created, from it, and it is strictly increasing.
+- IOUtilities has some nice APIs to close streams without extra try/catch blocks, and also has a nice transfer APIs, and transfer APIs that show call back with transfer stats.
+- ClassValueMap and ClassValueSet make using JDK's ClassValue much easier yet retain the benefits of ClassValue in terms of speed.
+- Of course, for case-insensitive Maps, there is no better one than java-util's CaseInsensitiveMap.
+- And if you need to create large amounts of Maps, CompactMap (and its variants) use significantly less space than regular JDK maps.
+
+## Commit Messages
+- Start with a short imperative summary (max ~50 characters).
+- Leave a blank line after the summary, then add further details if needed.
+- Don’t amend or rewrite existing commits.
+- Please list the Codex agent as the author so we can see that in the "Blame" view at the line number level.
+
+## Testing
+- Run `mvn -q test` before committing to ensure tests pass.
+- If tests can’t run due to environment limits, note this in the PR description.
+
+## Documentation
+- Update `changelog.md` with a bullet about your change.
+- Update `userguide.md` whenever you add or modify public-facing APIs.
+
+## Pull Request Notes
+- Summarize key changes and reference the main files touched.
+- Include a brief “Testing” section summarizing test results or noting any limitations.
+
diff --git a/badge.svg b/badge.svg
new file mode 100644
index 000000000..e5b781d12
--- /dev/null
+++ b/badge.svg
@@ -0,0 +1,28 @@
+
\ No newline at end of file
diff --git a/changelog.md b/changelog.md
index 09f551f17..43e2d56a6 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,141 +1,2363 @@
### Revision History
-* 1.26.0
- * Enhancement: added getClassNameFromByteCode() API to ReflectionUtils.
-* 1.25.1
- * Enhancement: The Delta object returned by GraphComparator implements Serializable for those using ObjectInputStream / ObjectOutputStream. Provided by @metlaivan (Ivan Metla)
-* 1.25.0
- * Performance improvement: `CaseInsensitiveMap/Set` internally adds Strings to Map without using .toLowerCase() which eliminates creating a temporary copy on the heap of the String being added, just to get its lowerCaseValue.
- * Performance improvement: `CaseInsensitiveMap/Set` uses less memory internally by caching the hash code as an int, instead of an Integer.
- * `StringUtilities.caseInsensitiveHashCode()` API added. This allows computing a case-insensitive hashcode from a String without any object creation (heap usage).
-* 1.24.0
- * `Converter.convert()` - performance improved using class instance comparison versus class String name comparison.
- * `CaseInsensitiveMap/Set` - performance improved. `CaseInsensitiveString` (internal) short-circuits on equality check if hashCode() [cheap runtime cost] is not the same. Also, all method returning true/false to detect if `Set` or `Map` changed rely on size() instead of contains.
-* 1.23.0
- * `Converter.convert()` API update: When a mutable type (`Date`, `AtomicInteger`, `AtomicLong`, `AtomicBoolean`) is passed in, and the destination type is the same, rather than return the instance passed in, a copy of the instance is returned.
-* 1.22.0
- * Added `GraphComparator` which is used to compute the difference (delta) between two object graphs. The generated `List` of Delta objects can be 'played' against the source to bring it up to match the target. Very useful in transaction processing systems.
-* 1.21.0
- * Added `Executor` which is used to execute Operating System commands. For example, `Executor exector = new Executor(); executor.exec("echo This is handy"); assertEquals("This is handy", executor.getOut().trim());`
- * bug fix: `CaseInsensitiveMap`, when passed a `LinkedHashMap`, was inadvertently using a HashMap instead.
-* 1.20.5
- * `CaseInsensitiveMap` intentionally does not retain 'not modifiability'.
- * `CaseInsensitiveSet` intentionally does not retain 'not modifiability'.
-* 1.20.4
- * Failed release. Do not use.
-* 1.20.3
- * `TrackingMap` changed so that `get(anyKey)` always marks it as keyRead. Same for `containsKey(anyKey)`.
- * `CaseInsensitiveMap` has a constructor that takes a `Map`, which allows it to take on the nature of the `Map`, allowing for case-insensitive `ConcurrentHashMap`, sorted `CaseInsensitiveMap`, etc. The 'Unmodifiable' `Map` nature is intentionally not taken on. The passed in `Map` is not mutated.
- * `CaseInsensitiveSet` has a constructor that takes a `Collection`, nwhich allows it to take on the nature of the `Collection`, allowing for sorted `CaseInsensitiveSets`. The 'unmodifiable' `Collection` nature is intentionally not taken on. The passed in `Set` is not mutated.
-* 1.20.2
- * `TrackingMap` changed so that an existing key associated to null counts as accessed. It is valid for many Map types to allow null values to be associated to the key.
- * `TrackingMap.getWrappedMap()` added so that you can fetch the wrapped Map.
-* 1.20.1
- * TrackingMap changed so that .put() does not mark the key as accessed.
-* 1.20.0
- * `TrackingMap` added. Create this map around any type of Map, and it will track which keys are accessed via .get(), .containsKey(), or .put() (when put overwrites a value already associated to the key). Provided by @seankellner.
-* 1.19.3
- * Bug fix: `CaseInsensitiveMap.entrySet()` - calling `entry.setValue(k, v)` while iterating the entry set, was not updating the underlying value. This has been fixed and test case added.
-* 1.19.2
- * The order in which system properties are read versus environment variables via the `SystemUtilities.getExternalVariable()` method has changed. System properties are checked first, then environment variables.
-* 1.19.1
- * Fixed issue in `DeepEquals.deepEquals()` where a Container type (`Map` or `Collection`) was being compared to a non-container - the result of this comparison was inconsistent. It is always false if a Container is compared to a non-container type (anywhere within the object graph), regardless of the comparison order A, B versus comparing B, A.
-* 1.19.0
- * `StringUtilities.createUtf8String(byte[])` API added which is used to easily create UTF-8 strings without exception handling code.
- * `StringUtilities.getUtf8Bytes(String s)` API added which returns a byte[] of UTF-8 bytes from the passed in Java String without any exception handling code required.
- * `ByteUtilities.isGzipped(bytes[])` API added which returns true if the `byte[]` represents gzipped data.
- * `IOUtilities.compressBytes(byte[])` API added which returns the gzipped version of the passed in `byte[]` as a `byte[]`
- * `IOUtilities.uncompressBytes(byte[])` API added which returns the original byte[] from the passed in gzipped `byte[]`.
- * JavaDoc issues correct to support Java 1.8 stricter JavaDoc compilation.
-* 1.18.1
- * `UrlUtilities` now allows for per-thread `userAgent` and `referrer` as well as maintains backward compatibility for setting these values globally.
- * `StringUtilities` `getBytes()` and `createString()` now allow null as input, and return null for output for null input.
- * Javadoc updated to remove errors flagged by more stringent Javadoc 1.8 generator.
-* 1.18.0
- * Support added for `Timestamp` in `Converter.convert()`
- * `null` can be passed into `Converter.convert()` for primitive types, and it will return their logical 0 value (0.0f, 0.0d, etc.). For primitive wrappers, atomics, etc, null will be returned.
- * "" can be passed into `Converter.convert()` and it will set primitives to 0, and the object types (primitive wrappers, dates, atomics) to null. `String` will be set to "".
-* 1.17.1
- * Added full support for `AtomicBoolean`, `AtomicInteger`, and `AtomicLong` to `Converter.convert(value, AtomicXXX)`. Any reasonable value can be converted to/from these, including Strings, Dates (`AtomicLong`), all `Number` types.
- * `IOUtilities.flush()` now supports `XMLStreamWriter`
-* 1.17.0
- * `UIUtilities.close()` now supports `XMLStreamReader` and `XMLStreamWriter` in addition to `Closeable`.
- * `Converter.convert(value, type)` - a value of null is supported, and returns null. A null type, however, throws an `IllegalArgumentException`.
-* 1.16.1
- * In `Converter.convert(value, type)`, the value is trimmed of leading / trailing white-space if it is a String and the type is a `Number`.
-* 1.16.0
- * Added `Converter.convert()` API. Allows converting instances of one type to another. Handles all primitives, primitive wrappers, `Date`, `java.sql.Date`, `String`, `BigDecimal`, and `BigInteger`. Additionally, input (from) argument accepts `Calendar`.
- * Added static `getDateFormat()` to `SafeSimpleDateFormat` for quick access to thread local formatter (per format `String`).
-* 1.15.0
- * Switched to use Log4J2 () for logging.
-* 1.14.1
- * bug fix: `CaseInsensitiveMa.keySet()` was only initializing the iterator once. If `keySet()` was called a 2nd time, it would no longer work.
-* 1.14.0
- * bug fix: `CaseInsensitiveSet()`, the return value for `addAll()`, `returnAll()`, and `retainAll()` was wrong in some cases.
-* 1.13.3
- * `EncryptionUtilities` - Added byte[] APIs. Makes it easy to encrypt/decrypt `byte[]` data.
- * `pom.xml` had extraneous characters inadvertently added to the file - these are removed.
- * 1.13.1 & 13.12 - issues with sonatype
-* 1.13.0
- * `DateUtilities` - Day of week allowed (properly ignored).
- * `DateUtilities` - First (st), second (nd), third (rd), and fourth (th) ... supported.
- * `DateUtilities` - The default toString() standard date / time displayed by the JVM is now supported as a parseable format.
- * `DateUtilities` - Extra whitespace can exist within the date string.
- * `DateUtilities` - Full time zone support added.
- * `DateUtilities` - The date (or date time) is expected to be in isolation. Whitespace on either end is fine, however, once the date time is parsed from the string, no other content can be left (prevents accidently parsing dates from dates embedded in text).
- * `UrlUtilities` - Removed proxy from calls to `URLUtilities`. These are now done through the JVM.
-* 1.12.0
- * `UniqueIdGenerator` uses 99 as the cluster id when the JAVA_UTIL_CLUSTERID environment variable or System property is not available. This speeds up execution on developer's environments when they do not specify `JAVA_UTIL_CLUSTERID`.
- * All the 1.11.x features rolled up.
-* 1.11.3
- * `UrlUtilities` - separated out call that resolves `res://` to a public API to allow for wider use.
-* 1.11.2
- * Updated so headers can be set individually by the strategy (`UrlInvocationHandler`)
- * `InvocationHandler` set to always uses `POST` method to allow additional `HTTP` headers.
-* 1.11.1
- * Better IPv6 support (`UniqueIdGenerator`)
- * Fixed `UrlUtilities.getContentFromUrl()` (`byte[]`) no longer setting up `SSLFactory` when `HTTP` protocol used.
-* 1.11.0
- * `UrlInvocationHandler`, `UrlInvocationStrategy` - Updated to allow more generalized usage. Pass in your implementation of `UrlInvocationStrategy` which allows you to set the number of retry attempts, fill out the URL pattern, set up the POST data, and optionally set/get cookies.
- * Removed dependency on json-io. Only remaining dependency is Apache commons-logging.
-* 1.10.0
- * Issue #3 fixed: `DeepEquals.deepEquals()` allows similar `Map` (or `Collection`) types to be compared without returning 'not equals' (false). Example, `HashMap` and `LinkedHashMap` are compared on contents only. However, compare a `SortedSet` (like `TreeMap`) to `HashMap` would fail unless the Map keys are in the same iterative order.
- * Tests added for `UrlUtilities`
- * Tests added for `Traverser`
-* 1.9.2
- * Added wildcard to regex pattern to `StringUtilities`. This API turns a DOS-like wildcard pattern (where * matches anything and ? matches a single character) into a regex pattern useful in `String.matches()` API.
-* 1.9.1
- * Floating-point allow difference by epsilon value (currently hard-coded on `DeepEquals`. Will likely be optional parameter in future version).
-* 1.9.0
- * `MathUtilities` added. Currently, variable length `minimum(arg0, arg1, ... argn)` and `maximum()` functions added. Available for `long`, `double`, `BigInteger`, and `BigDecimal`. These cover the smaller types.
- * `CaseInsensitiveMap` and `CaseInsensitiveSet` `keySet()` and `entrySet()` are faster as they do not make a copy of the entries. Internally, `CaseInsensitiveString` caches it's hash, speeding up repeated access.
- * `StringUtilities levenshtein()` and `damerauLevenshtein()` added to compute edit length. See Wikipedia for understand of the difference. Currently recommend using `levenshtein()` as it uses less memory.
- * The Set returned from the `CaseInsensitiveMap.entrySet()` now contains mutable entry's (value-side). It had been using an immutable entry, which disallowed modification of the value-side during entry walk.
-* 1.8.4
- * `UrlUtilities`, fixed issue where the default settings for the connection were changed, not the settings on the actual connection.
-* 1.8.3
- * `ReflectionUtilities` has new `getClassAnnotation(classToCheck, annotation)` API which will return the annotation if it exists within the classes super class hierarchy or interface hierarchy. Similarly, the `getMethodAnnotation()` API does the same thing for method annotations (allow inheritance - class or interface).
-* 1.8.2
- * `CaseInsensitiveMap` methods `keySet()` and `entrySet()` return Sets that are identical to how the JDK returns 'view' Sets on the underlying storage. This means that all operations, besides `add()` and `addAll()`, are supported.
- * `CaseInsensitiveMap.keySet()` returns a `Set` that is case insensitive (not a `CaseInsensitiveSet`, just a `Set` that ignores case). Iterating this `Set` properly returns each originally stored item.
-* 1.8.1
- * Fixed `CaseInsensitiveMap() removeAll()` was not removing when accessed via `keySet()`
-* 1.8.0
- * Added `DateUtilities`. See description above.
-* 1.7.4
- * Added "res" protocol (resource) to `UrlUtilities` to allow files from classpath to easily be loaded. Useful for testing.
-* 1.7.2
- * `UrlUtilities.getContentFromUrl() / getContentFromUrlAsString()` - removed hard-coded proxy server name
-* 1.7.1
- * `UrlUtilities.getContentFromUrl() / getContentFromUrlAsString()` - allow content to be fetched as `String` or binary (`byte[]`).
-* 1.7.0
- * `SystemUtilities` added. New API to fetch value from environment or System property
- * `UniqueIdGenerator` - checks for environment variable (or System property) JAVA_UTIL_CLUSTERID (0-99). Will use this if set, otherwise last IP octet mod 100.
-* 1.6.1
- * Added: `UrlUtilities.getContentFromUrl()`
-* 1.6.0
- * Added `CaseInsensitiveSet`.
-* 1.5.0
- * Fixed: `CaseInsensitiveMap's iterator.remove()` method, it did not remove items.
- * Fixed: `CaseInsensitiveMap's equals()` method, it required case to match on keys.
-* 1.4.0
- * Initial version
+
+#### 4.104.0 - (Unreleased)
+
+#### 4.103.0 - 2026-05-25
+* **BUILD**: Test-scope dependency bumps — `junit-jupiter` 5.14.3 → 5.14.4; `agrona` 1.22.0 → 1.23.1 (still JDK 8 compatible; agrona 2.x dropped Java 8).
+* **BUILD**: Registered the JDK 9+ standard `@apiNote` / `@implSpec` / `@implNote` tags with `maven-javadoc-plugin` so the javadoc tool no longer emits "unknown tag" warnings for these.
+* **FEATURE**: New `com.cedarsoftware.util.internal.VectorizedArrays` — exposes `equalsRange` / `mismatchRange` / `compareRange` for both `char[]` and `byte[]`. Dispatches at runtime to JDK 9+ SIMD-vectorized `Arrays.*` intrinsics, with hand-rolled loop fallbacks for JDK 8. Resolution happens once at class load via `MethodHandle`s, so per-call cost is a static-field read + `invokeExact`. Internal package for now; promoted to public API once the contract stabilizes. 21 new tests.
+* **FEATURE**: `Converter.convert(String, byte[].class)` (and transitively `String → ByteBuffer`) performs multi-format detection — tries stringified JSON number arrays, spaced hex, unspaced hex (length ≥ 8 — catches `CAFEBABE`/`DEADBEEF` magic numbers, compact UUIDs, SHA hashes), URL-safe Base64, then standard Base64. Tightness rules (length, padding, alphabet symbols) prevent short tokens like `"DATA"`/`"ABCD"` from being mis-classified and let them fall through to the historical charset path.
+* **FEATURE**: New `MapConversions.toByteArray` (and refactored `toByteBuffer`) handles the wrapped-form contract `{"@type":"
+ * This method performs a simple linear search for the pattern within the data array. + * It is useful for locating byte sequences such as markers, headers, or placeholders + * within binary data. + *
+ * + *{@code
+ * byte[] data = {0x00, 0x01, 0x02, 0x03, 0x04, 0x02, 0x03};
+ * byte[] pattern = {0x02, 0x03};
+ * int index = ByteUtilities.indexOf(data, pattern, 0); // Returns 2
+ * int next = ByteUtilities.indexOf(data, pattern, 3); // Returns 5
+ * }
+ *
+ * @param data the byte array to search within
+ * @param pattern the byte pattern to find
+ * @param start the index to start searching from (inclusive)
+ * @return the index of the first occurrence of the pattern, or -1 if not found
+ * or if any parameter is invalid (null arrays, negative start, etc.)
+ */
+ public static int indexOf(byte[] data, byte[] pattern, int start) {
+ if (data == null || pattern == null || start < 0 || pattern.length == 0) {
+ return -1;
+ }
+ final int dataLen = data.length;
+ final int patternLen = pattern.length;
+ if (patternLen > dataLen || start > dataLen - patternLen) {
+ return -1;
+ }
+
+ // Fast path for single-byte patterns
+ if (patternLen == 1) {
+ byte target = pattern[0];
+ for (int i = start; i < dataLen; i++) {
+ if (data[i] == target) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ final byte first = pattern[0];
+ final byte last = pattern[patternLen - 1];
+ final int limit = dataLen - patternLen;
+ outer:
+ for (int i = start; i <= limit; i++) {
+ if (data[i] != first || data[i + patternLen - 1] != last) {
+ continue;
+ }
+ for (int j = 1; j < patternLen - 1; j++) {
+ if (data[i + j] != pattern[j]) {
+ continue outer;
+ }
+ }
+ return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Finds the last occurrence of a byte pattern within a byte array, searching backwards from the specified index.
+ * + * This method searches backwards from the start position to find the last occurrence + * of the pattern. It is useful for locating byte sequences when you need the rightmost match. + *
+ * + *{@code
+ * byte[] data = {0x02, 0x03, 0x00, 0x02, 0x03};
+ * byte[] pattern = {0x02, 0x03};
+ * int index = ByteUtilities.lastIndexOf(data, pattern, data.length - 1); // Returns 3
+ * int prev = ByteUtilities.lastIndexOf(data, pattern, 2); // Returns 0
+ * }
+ *
+ * @param data the byte array to search within
+ * @param pattern the byte pattern to find
+ * @param start the index to start searching backwards from (inclusive)
+ * @return the index of the last occurrence of the pattern, or -1 if not found
+ * or if any parameter is invalid (null arrays, negative start, etc.)
+ */
+ public static int lastIndexOf(byte[] data, byte[] pattern, int start) {
+ if (data == null || pattern == null || start < 0 || pattern.length == 0) {
+ return -1;
+ }
+ final int dataLen = data.length;
+ final int patternLen = pattern.length;
+ if (patternLen > dataLen) {
+ return -1;
+ }
+
+ // Adjust start to the last valid position where pattern could fit
+ int effectiveStart = Math.min(start, dataLen - patternLen);
+ if (effectiveStart < 0) {
+ return -1;
+ }
+
+ // Fast path for single-byte patterns
+ if (patternLen == 1) {
+ byte target = pattern[0];
+ for (int i = effectiveStart; i >= 0; i--) {
+ if (data[i] == target) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ final byte first = pattern[0];
+ final byte last = pattern[patternLen - 1];
+ outer:
+ for (int i = effectiveStart; i >= 0; i--) {
+ if (data[i] != first || data[i + patternLen - 1] != last) {
+ continue;
+ }
+ for (int j = 1; j < patternLen - 1; j++) {
+ if (data[i + j] != pattern[j]) {
+ continue outer;
+ }
+ }
+ return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Finds the last occurrence of a byte pattern within a byte array.
+ * + * This is a convenience method that searches from the end of the data array. + *
+ * + * @param data the byte array to search within + * @param pattern the byte pattern to find + * @return the index of the last occurrence of the pattern, or -1 if not found + * or if any parameter is invalid + * @see #lastIndexOf(byte[], byte[], int) + */ + public static int lastIndexOf(byte[] data, byte[] pattern) { + if (data == null) { + return -1; + } + return lastIndexOf(data, pattern, data.length - 1); + } /** + * Checks if a byte array contains the specified byte pattern. *- * {@code StringUtilities} instances should NOT be constructed in standard - * programming. Instead, the class should be used statically as - * {@code StringUtilities.trim();}. + * This is a convenience method equivalent to {@code indexOf(data, pattern, 0) >= 0}. *
+ * + *{@code
+ * byte[] data = {0x00, 0x01, 0x02, 0x03};
+ * byte[] pattern = {0x01, 0x02};
+ * boolean found = ByteUtilities.contains(data, pattern); // Returns true
+ * }
+ *
+ * @param data the byte array to search within
+ * @param pattern the byte pattern to find
+ * @return true if the pattern is found within data, false otherwise
*/
- private ByteUtilities() {
- super();
- }
-
- // Turn hex String into byte[]
- // If string is not even length, return null.
-
- public static byte[] decode(final String s)
- {
- int len = s.length();
- if (len % 2 != 0)
- {
- return null;
- }
-
- byte[] bytes = new byte[len / 2];
- int pos = 0;
-
- for (int i = 0; i < len; i += 2)
- {
- byte hi = (byte)Character.digit(s.charAt(i), 16);
- byte lo = (byte)Character.digit(s.charAt(i + 1), 16);
- bytes[pos++] = (byte)(hi * 16 + lo);
- }
-
- return bytes;
- }
-
- /**
- * Convert a byte array into a printable format containing a String of hex
- * digit characters (two per byte).
- *
- * @param bytes array representation
- * @return String hex digits
- */
- public static String encode(final byte[] bytes)
- {
- StringBuilder sb = new StringBuilder(bytes.length << 1);
- for (byte aByte : bytes)
- {
- sb.append(convertDigit(aByte >> 4));
- sb.append(convertDigit(aByte & 0x0f));
- }
- return sb.toString();
- }
-
- /**
- * Convert the specified value (0 .. 15) to the corresponding hex digit.
- *
- * @param value
- * to be converted
- * @return '0'..'F' in char format.
- */
- private static char convertDigit(final int value)
- {
- return _hex[value & 0x0f];
- }
-
- /**
- * @param bytes byte[] of bytes to test
- * @return true if bytes are gzip compressed, false otherwise.
- */
- public static boolean isGzipped(byte[] bytes)
- {
- return bytes[0] == (byte)0x1f && bytes[1] == (byte)0x8b;
- }
+ public static boolean contains(byte[] data, byte[] pattern) {
+ return indexOf(data, pattern, 0) >= 0;
+ }
}
diff --git a/src/main/java/com/cedarsoftware/util/CaseInsensitiveMap.java b/src/main/java/com/cedarsoftware/util/CaseInsensitiveMap.java
index c0157b4ad..a00b39221 100644
--- a/src/main/java/com/cedarsoftware/util/CaseInsensitiveMap.java
+++ b/src/main/java/com/cedarsoftware/util/CaseInsensitiveMap.java
@@ -1,43 +1,157 @@
package com.cedarsoftware.util;
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Array;
import java.util.AbstractMap;
import java.util.AbstractSet;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
import java.util.Set;
+import java.util.SortedMap;
import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
-import static com.cedarsoftware.util.StringUtilities.hashCodeIgnoreCase;
/**
- * Useful Map that does not care about the case-sensitivity of keys
- * when the key value is a String. Other key types can be used.
- * String keys will be treated case insensitively, yet key case will
- * be retained. Non-string keys will work as they normally would.
+ * A Map implementation that provides case-insensitive key comparison for {@link String} keys, while preserving
+ * the original case of the keys. Non-String keys are treated as they would be in a regular {@link Map}.
+ *
+ * This Map is conditionally thread-safe based on if the backing map implementation is a thread-safe.
+ * + *When the backing map is a {@link MultiKeyMap}, this map also supports multi-key operations + * with case-insensitive String key handling. Works with 1D keys (no collections or arrays in keys)
+ * + *ConcurrentMap Implementation: This class implements {@link ConcurrentMap} and provides + * all concurrent operations ({@code putIfAbsent}, {@code replace}, bulk operations, etc.) with case-insensitive + * semantics. Thread safety depends entirely on the backing map implementation:
+ *Choose your backing map implementation based on your concurrency requirements.
+ * + *{@code
+ * // Create a case-insensitive map with default LinkedHashMap backing (not thread-safe)
+ * CaseInsensitiveMap map = new CaseInsensitiveMap<>();
+ * map.put("Key", "Value");
+ * LOG.info(map.get("key")); // Outputs: Value
+ * LOG.info(map.get("KEY")); // Outputs: Value
+ *
+ * // Create a thread-safe case-insensitive map with ConcurrentHashMap backing
+ * ConcurrentMap concurrentMap = CaseInsensitiveMap.concurrent();
+ * concurrentMap.putIfAbsent("Key", "Value");
+ * LOG.info(concurrentMap.get("key")); // Outputs: Value (thread-safe)
+ *
+ * // Alternative: explicit constructor approach
+ * ConcurrentMap explicitMap = new CaseInsensitiveMap<>(Collections.emptyMap(), new ConcurrentHashMap<>());
+ *
+ * // Create a case-insensitive map from an existing map
+ * Map source = Map.of("Key1", "Value1", "Key2", "Value2");
+ * CaseInsensitiveMap copiedMap = new CaseInsensitiveMap<>(source);
+ *
+ * // Use with non-String keys
+ * CaseInsensitiveMap intKeyMap = new CaseInsensitiveMap<>();
+ * intKeyMap.put(1, "One");
+ * LOG.info(intKeyMap.get(1)); // Outputs: One
+ * }
+ *
+ * + * The backing map implementation is automatically chosen based on the type of the source map or can be explicitly + * specified. For example: + *
+ *+ * CaseInsensitiveMap implements {@link ConcurrentMap} and provides all concurrent operations + * ({@code putIfAbsent}, {@code replace}, {@code remove(key, value)}, bulk operations, etc.) with + * case-insensitive semantics. Thread safety is determined by the backing map implementation: + *
+ *- * The internal CaseInsensitiveString is never exposed externally - * from this class. When requesting the keys or entries of this map, - * or calling containsKey() or get() for example, use a String as you - * normally would. The returned Set of keys for the keySet() and - * entrySet() APIs return the original Strings, not the internally - * wrapped CaseInsensitiveString. + * Recommendation: For multi-threaded applications, explicitly choose a concurrent + * backing map implementation to ensure thread safety. + *
* - * As an added benefit, .keySet() returns a case-insenstive - * Set, however, again, the contents of the entries are actual Strings. - * Similarly, .entrySet() returns a case-insensitive entry set, such that - * .getKey() on the entry is case insensitive when compared, but the - * returned key is a String. + *Concrete or known map types are matched to their corresponding internal maps (e.g. TreeMap to TreeMap). + * If no specific match is found, a LinkedHashMap is used by default.
+ * + * @param source the map whose mappings are to be placed in this map. Must not be null. + * @throws NullPointerException if the source map is null + */ + public CaseInsensitiveMap(MapString keys are handled case-insensitively.
+ *When backing map is MultiKeyMap, this method supports 1D Collections and Arrays with case-insensitive String handling.
+ */ + @Override + public V get(Object key) { + if (isMultiKeyMapBacking) { + return map.get(convertKeyForMultiKeyMap(key)); + } + if (key instanceof String) { + if (useLookupKey) { + LookupKey lk = LOOKUP_KEY.get(); + lk.set((String) key); + return map.get(lk); + } + return map.get(new CaseInsensitiveString((String) key)); } + return map.get(key); + } - for (Map.Entry entry : m.entrySet()) - { - put((K) entry.getKey(), (V) entry.getValue()); + /** + * {@inheritDoc} + *String keys are handled case-insensitively.
+ *When backing map is MultiKeyMap, this method supports 1D Collections and Arrays with case-insensitive String handling.
+ */ + @Override + public boolean containsKey(Object key) { + if (isMultiKeyMapBacking) { + return map.containsKey(convertKeyForMultiKeyMap(key)); } + if (key instanceof String) { + if (useLookupKey) { + LookupKey lk = LOOKUP_KEY.get(); + lk.set((String) key); + return map.containsKey(lk); + } + return map.containsKey(new CaseInsensitiveString((String) key)); + } + return map.containsKey(key); } - public V remove(Object key) - { - if (key instanceof String) - { - String keyString = (String) key; - return map.remove(new CaseInsensitiveString(keyString)); + /** + * {@inheritDoc} + *String keys are stored case-insensitively.
+ *When backing map is MultiKeyMap, this method supports 1D Collections and Arrays with case-insensitive String handling.
+ */ + @SuppressWarnings("unchecked") + @Override + public V put(K key, V value) { + if (isMultiKeyMapBacking) { + return map.put((K) convertKeyForMultiKeyMap(key), value); + } + return map.put((K) convertKey(key), value); + } + + /** + * {@inheritDoc} + *String keys are handled case-insensitively.
+ *When backing map is MultiKeyMap, this method supports 1D Collections and Arrays with case-insensitive String handling.
+ */ + @Override + public V remove(Object key) { + if (isMultiKeyMapBacking) { + return map.remove(convertKeyForMultiKeyMap(key)); + } + if (key instanceof String) { + if (useLookupKey) { + LookupKey lk = LOOKUP_KEY.get(); + lk.set((String) key); + return map.remove(lk); + } + return map.remove(new CaseInsensitiveString((String) key)); } return map.remove(key); } - // delegates - public int size() - { + /** + * {@inheritDoc} + *Delegates directly to the backing map, bypassing the {@code AbstractMap.size()} + * implementation which routes through {@code entrySet().size()}.
+ */ + @Override + public int size() { return map.size(); } - public boolean isEmpty() - { + /** + * {@inheritDoc} + *Delegates directly to the backing map, bypassing the {@code AbstractMap.isEmpty()} + * implementation which routes through {@code size()}.
+ */ + @Override + public boolean isEmpty() { return map.isEmpty(); } - public boolean equals(Object other) - { - if (other == this) return true; - if (!(other instanceof Map)) return false; + // ===== PRIVATE HELPER METHODS ===== + + /** + * Handles array and collection keys for MultiKeyMap operations. + * Converts String keys to case-insensitive equivalents and handles different array types appropriately. + * + * @param key the key to process (can be array, collection, or single object) + * @param operation a function that takes the processed key and returns the result + * @return the result of the operation, or null if not a MultiKeyMap or not an array/collection + */ + + // ===== MULTI-KEY APIs ===== + + /** + * Stores a value with multiple keys, applying case-insensitive handling to String keys. + * This method is only supported when the backing map is a MultiKeyMap. + * + *Examples:
+ *{@code
+ * CaseInsensitiveMap map = new CaseInsensitiveMap<>(Collections.emptyMap(), new MultiKeyMap<>());
+ *
+ * // Multi-key operations with case-insensitive String handling
+ * map.putMultiKey("Value1", "DEPT", "Engineering"); // String keys converted to case-insensitive
+ * map.putMultiKey("Value2", "dept", "Marketing", "West"); // Mixed case handled automatically
+ * map.putMultiKey("Value3", 123, "project", "Alpha"); // Mixed String and non-String keys
+ *
+ * // Retrieval with case-insensitive matching
+ * String val1 = map.getMultiKey("dept", "ENGINEERING"); // Returns "Value1"
+ * String val2 = map.getMultiKey("DEPT", "marketing", "west"); // Returns "Value2"
+ * }
+ *
+ * @param value the value to store
+ * @param keys the key components (unlimited number, String keys are handled case-insensitively)
+ * @return the previous value associated with the key, or null if there was no mapping
+ * @throws IllegalStateException if the backing map is not a MultiKeyMap instance
+ */
+
+ /**
+ * {@inheritDoc}
+ * Equality is based on case-insensitive comparison for String keys.
+ */ + @Override + public boolean equals(Object other) { + if (other == this) { return true; } + if (!(other instanceof Map)) { return false; } Map, ?> that = (Map, ?>) other; - if (that.size() != size()) - { - return false; - } + if (that.size() != size()) { return false; } - for (Map.Entry entry : that.entrySet()) - { - final Object thatKey = entry.getKey(); - if (!containsKey(thatKey)) - { + MapIf the set fits in the specified array with room to spare (i.e., the array has more + * elements than the set), the element in the array immediately following the end of the set + * is set to null. This is useful in determining the length of the set only if the caller + * knows that the set does not contain any null elements. + * + *
String keys are returned in their original form rather than their case-insensitive + * representation used internally by the map. + * + *
This method could be removed and the parent class method would work, however, it's more efficient:
+ * It works directly with the backing map's keySet instead of using an iterator.
+ *
+ * @param a the array into which the elements of this set are to be stored,
+ * if it is big enough; otherwise, a new array of the same runtime
+ * type is allocated for this purpose
+ * @return an array containing the elements of this set
+ * @throws ArrayStoreException if the runtime type of the specified array
+ * is not a supertype of the runtime type of every element in this set
+ * @throws NullPointerException if the specified array is null
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public Retains only the elements in this set that are contained in the specified collection.
+ * In other words, removes from this set all of its elements that are not contained
+ * in the specified collection. The comparison is case-insensitive.
+ *
+ * This operation creates a temporary CaseInsensitiveMap to perform case-insensitive
+ * comparison of elements, then removes all keys from the underlying map that are not
+ * present in the specified collection.
+ *
+ * @param c collection containing elements to be retained in this set
+ * @return true if this set changed as a result of the call
+ * @throws ClassCastException if the types of one or more elements in this set
+ * are incompatible with the specified collection
+ * @SuppressWarnings("unchecked") suppresses unchecked cast warnings as elements
+ * are assumed to be of type K
+ */
+ @Override
+ public boolean retainAll(Collection> c) {
+ // Normalize collection keys for case-insensitive comparison
+ Set Returns a Set view of the entries contained in this map. Each entry returns its key in the
+ * original String form (if it was a String). Operations on this set affect the underlying map. Returns the number of entries in the underlying map. Determines if the specified object is an entry present in the map. String keys are
+ * matched case-insensitively. Returns an array containing all the entries in this set. Each entry returns its key in the
+ * original String form if it was originally a String. Returns an array containing all the entries in this set. The runtime type of the returned
+ * array is that of the specified array. Removes the specified entry from the underlying map if present. Removes all entries in the specified collection from the underlying map, if present. Retains only the entries in this set that are contained in the specified collection. Returns an iterator over the entries in the map. Each returned entry will provide
+ * the key in its original form if it was originally a String. Returns the key in its original String form if it was originally stored as a String,
+ * otherwise returns the key as is. Sets the value associated with this entry's key in the underlying map.
+ * For String keys, equality is based on the original String value rather than
+ * the case-insensitive representation. This ensures that entries with the same
+ * case-insensitive key but different original strings are considered distinct.
+ *
+ * @param o object to be compared for equality with this map entry
+ * @return true if the specified object is equal to this map entry
+ * @see Entry#equals(Object)
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Entry)) return false;
+ Entry, ?> e = (Entry, ?>) o;
+ return Objects.equals(getOriginalKey(), e.getKey()) &&
+ Objects.equals(getValue(), e.getValue());
}
- public Object[] toArray()
- {
- Object[] items = new Object[size()];
- int i=0;
- for (Object key : map.keySet())
- {
- items[i++] = key instanceof CaseInsensitiveString ? key.toString() : key;
+ /**
+ * {@inheritDoc}
+ *
+ * For String keys, the hash code is computed using the original String value
+ * rather than the case-insensitive representation.
+ *
+ * @return the hash code value for this map entry
+ * @see Entry#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getOriginalKey()) ^ Objects.hashCode(getValue());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Returns a string representation of this map entry. The string representation
+ * consists of this entry's key followed by the equals character ("=") followed
+ * by this entry's value. For String keys, the original string value is used.
+ *
+ * @return a string representation of this map entry
+ */
+ @Override
+ public String toString() {
+ return getKey() + "=" + getValue();
+ }
+ }
+
+ /**
+ * Wrapper class for String keys to enforce case-insensitive comparison.
+ * Implements CharSequence for compatibility with String operations and
+ * Serializable for persistence support.
+ */
+ public static final class CaseInsensitiveString implements Comparable This wrapper ensures users' Function implementations receive the same key type they originally
+ * put into the map, maintaining the map's encapsulation of its case-insensitive implementation. Thread-safe: uses immutable CaseInsensitiveString objects and thread-safe unwrapping. This wrapper ensures users' BiFunction implementations receive the same key type they originally
+ * put into the map, maintaining the map's encapsulation of its case-insensitive implementation. Thread-safe: uses immutable CaseInsensitiveString objects and thread-safe unwrapping.
+ * For String keys, the mapping is performed in a case-insensitive manner. If the mapping
+ * function receives a String key, it will be passed the original String rather than the
+ * internal case-insensitive representation.
+ *
+ * @see Map#computeIfAbsent(Object, Function)
+ */
+ @Override
+ public V computeIfAbsent(K key, Function super K, ? extends V> mappingFunction) {
+ // mappingFunction gets wrapped so it sees the original String if k is a CaseInsensitiveString
+ return map.computeIfAbsent(convertConcurrentKey(key), wrapFunctionForKey(mappingFunction));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * For String keys, the mapping is performed in a case-insensitive manner. If the remapping
+ * function receives a String key, it will be passed the original String rather than the
+ * internal case-insensitive representation.
+ *
+ * @see Map#computeIfPresent(Object, BiFunction)
+ */
+ @Override
+ public V computeIfPresent(K key, BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ // Normalize input key to ensure case-insensitive lookup for Strings
+ // remappingFunction gets wrapped so it sees the original String if k is a CaseInsensitiveString
+ return map.computeIfPresent(convertConcurrentKey(key), wrapBiFunctionForKey(remappingFunction));
}
- private class EntrySet
+ * For String keys, the computation is performed in a case-insensitive manner. If the remapping
+ * function receives a String key, it will be passed the original String rather than the
+ * internal case-insensitive representation.
+ *
+ * @see Map#compute(Object, BiFunction)
+ */
+ @Override
+ public V compute(K key, BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ // Wrapped so that the BiFunction receives original String key if applicable
+ return map.compute(convertConcurrentKey(key), wrapBiFunctionForKey(remappingFunction));
+ }
- EntrySet() { }
+ /**
+ * {@inheritDoc}
+ *
+ * For String keys, the merge is performed in a case-insensitive manner. The remapping
+ * function operates only on values and is not affected by case sensitivity.
+ *
+ * @see Map#merge(Object, Object, BiFunction)
+ */
+ @Override
+ public V merge(K key, V value, BiFunction super V, ? super V, ? extends V> remappingFunction) {
+ // merge doesn't provide the key to the BiFunction, only values. No wrapping of keys needed.
+ // The remapping function only deals with values, so we do not need wrapBiFunctionForKey here.
+ return map.merge(convertConcurrentKey(key), value, remappingFunction);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * For String keys, the operation is performed in a case-insensitive manner.
+ *
+ * @see Map#putIfAbsent(Object, Object)
+ */
+ @Override
+ public V putIfAbsent(K key, V value) {
+ return map.putIfAbsent(convertConcurrentKey(key), value);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * For String keys, the removal is performed in a case-insensitive manner.
+ *
+ * @see Map#remove(Object, Object)
+ */
+ @Override
+ public boolean remove(Object key, Object value) {
+ return map.remove(convertConcurrentKey(key), value);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * For String keys, the replacement is performed in a case-insensitive manner.
+ *
+ * @see Map#replace(Object, Object, Object)
+ */
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ return map.replace(convertConcurrentKey(key), oldValue, newValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * For String keys, the replacement is performed in a case-insensitive manner.
+ *
+ * @see Map#replace(Object, Object)
+ */
+ @Override
+ public V replace(K key, V value) {
+ return map.replace(convertConcurrentKey(key), value);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * For String keys, the action receives the original String key rather than the
+ * internal case-insensitive representation.
+ *
+ * @see Map#forEach(BiConsumer)
+ */
+ @Override
+ public void forEach(BiConsumer super K, ? super V> action) {
+ // Unwrap keys before calling action
+ map.forEach((k, v) -> action.accept(unwrapKey(k), v));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * For String keys, the function receives the original String key rather than the
+ * internal case-insensitive representation. The replacement is performed in a
+ * case-insensitive manner.
+ *
+ * @see Map#replaceAll(BiFunction)
+ */
+ @Override
+ public void replaceAll(BiFunction super K, ? super V, ? extends V> function) {
+ // Unwrap keys before applying the function to values
+ map.replaceAll((k, v) -> function.apply(unwrapKey(k), v));
+ }
- public int size()
- {
- return map.size();
+ /**
+ * Returns the number of mappings. This method should be used instead of {@link #size()} because
+ * a ConcurrentHashMap may contain more mappings than can be represented as an int. The value
+ * returned is an estimate; the actual count may differ if there are concurrent insertions or removals.
+ *
+ * This method delegates to {@link ConcurrentHashMap#mappingCount()} when the backing map
+ * is a ConcurrentHashMap, otherwise returns {@link #size()}. For String keys, the action receives the original String key rather than the
+ * internal case-insensitive representation. For String keys, the action receives the original String key rather than the
+ * internal case-insensitive representation. For String keys, the search function receives the original String key rather than the
+ * internal case-insensitive representation. For String keys, the transformer and reducer receive the original String key rather than the
+ * internal case-insensitive representation.
+ * The backing map for this set can be customized using various constructors:
+ *
+ * Thread safety depends entirely on the thread safety of the chosen backing map:
+ *
+ * This implementation uses {@link Collections#newSetFromMap(Map)} internally to create a Set view over
+ * a {@link CaseInsensitiveMap}. This provides a clean, efficient implementation that leverages the
+ * proven JDK Collections framework while maintaining case-insensitive semantics for String elements.
+ *
+ * The following methods are deprecated and retained for backward compatibility:
+ *
+ * This constructor is useful for creating a case-insensitive set with predictable iteration order
+ * and default configuration.
+ *
+ * The backing map is chosen based on the type of the input collection:
+ *
+ * This constructor allows full control over the underlying map implementation, enabling custom behavior
+ * for the set.
+ *
+ * This constructor is useful for creating a set with a predefined capacity to reduce resizing overhead
+ * during population.
+ *
+ * This constructor allows fine-grained control over the performance characteristics of the backing map.
+ *
+ * For {@link String} elements, the hash code computation is case-insensitive, as it relies on the
+ * case-insensitive hash codes provided by the underlying {@link CaseInsensitiveMap}.
+ *
+ * For {@link String} elements, equality is determined in a case-insensitive manner, ensuring that
+ * two sets containing equivalent strings with different cases (e.g., "Hello" and "hello") are considered equal.
+ *
+ * Returns the number of elements in this set. For {@link String} elements, the count is determined
+ * in a case-insensitive manner, ensuring that equivalent strings with different cases (e.g., "Hello" and "hello")
+ * are counted as a single element.
+ *
+ * Returns {@code true} if this set contains no elements. For {@link String} elements, the check
+ * is performed in a case-insensitive manner, ensuring that equivalent strings with different cases
+ * are treated as a single element.
+ *
+ * Returns {@code true} if this set contains the specified element. For {@link String} elements,
+ * the check is performed in a case-insensitive manner, meaning that strings differing only by case
+ * (e.g., "Hello" and "hello") are considered equal.
+ *
+ * Returns an iterator over the elements in this set. For {@link String} elements, the iterator
+ * preserves the original case of the strings, even though the set performs case-insensitive
+ * comparisons.
+ *
+ * When the backing map is a ConcurrentHashMap, the returned iterator is weakly consistent and
+ * will not throw {@link java.util.ConcurrentModificationException}. The iterator may reflect
+ * updates made during traversal, but is not required to do so.
+ *
+ * Returns an array containing all the elements in this set. For {@link String} elements, the array
+ * preserves the original case of the strings, even though the set performs case-insensitive
+ * comparisons.
+ *
+ * Returns an array containing all the elements in this set. The runtime type of the returned array
+ * is that of the specified array. For {@link String} elements, the array preserves the original
+ * case of the strings, even though the set performs case-insensitive comparisons.
+ *
+ * Adds the specified element to this set if it is not already present. For {@link String} elements,
+ * the addition is case-insensitive, meaning that strings differing only by case (e.g., "Hello" and
+ * "hello") are considered equal, and only one instance is added to the set.
+ *
+ * Removes the specified element from this set if it is present. For {@link String} elements, the
+ * removal is case-insensitive, meaning that strings differing only by case (e.g., "Hello" and "hello")
+ * are treated as equal, and removing any of them will remove the corresponding entry from the set.
+ *
+ * Returns {@code true} if this set contains all of the elements in the specified collection. For
+ * {@link String} elements, the comparison is case-insensitive, meaning that strings differing only by
+ * case (e.g., "Hello" and "hello") are treated as equal.
+ *
+ * Adds all the elements in the specified collection to this set if they're not already present.
+ * For {@link String} elements, the addition is case-insensitive, meaning that strings differing
+ * only by case (e.g., "Hello" and "hello") are treated as equal, and only one instance is added
+ * to the set.
+ *
+ * Retains only the elements in this set that are contained in the specified collection.
+ * For {@link String} elements, the comparison is case-insensitive, meaning that strings
+ * differing only by case (e.g., "Hello" and "hello") are treated as equal.
+ *
+ * Removes from this set all of its elements that are contained in the specified collection.
+ * For {@link String} elements, the removal is case-insensitive, meaning that strings differing
+ * only by case (e.g., "Hello" and "hello") are treated as equal, and removing any of them will
+ * remove the corresponding entry from the set.
+ *
+ * This override is required because {@link java.util.AbstractSet#removeAll} has a size-based
+ * optimization that, when {@code this.size() <= c.size()}, iterates over {@code this} and calls
+ * {@code c.contains()} — which is case-sensitive for non-CaseInsensitive collections.
+ * By always iterating over {@code c} and calling {@code this.remove()}, case-insensitive
+ * semantics are preserved regardless of the collection type passed in.
+ *
+ * Removes all elements from this set. After this call, the set will be empty.
+ * For {@link String} elements, the case-insensitive behavior of the set has no impact
+ * on the clearing operation.
+ *
+ * The spliterator reports {@link Spliterator#DISTINCT}. The spliterator's comparator
+ * is {@code null} if the set's comparator is {@code null}. Otherwise, the spliterator's
+ * comparator is the same as or imposes the same total ordering as the set's comparator.
+ *
+ * Errors or runtime exceptions thrown during iteration or by the predicate are relayed
+ * to the caller. For {@link String} elements, the removal is case-insensitive.
+ *
+ * Actions are performed in the order of iteration (if an iteration order is specified).
+ * Exceptions thrown by the action are relayed to the caller.
+ *
+ * When the backing map is not a ConcurrentHashMap, this method delegates to {@link #size()}.
+ * The estimate may not reflect recent additions or removals due to concurrent modifications.
+ *
+ * This method provides high-performance parallel iteration over set elements when using
+ * concurrent backing maps. The parallelism threshold determines the minimum set size
+ * required to enable parallel processing.
+ *
+ * This method provides high-performance parallel search over set elements when using
+ * concurrent backing maps. The search terminates early upon finding the first non-null result.
+ *
+ * This method provides high-performance parallel reduction over set elements when using
+ * concurrent backing maps. The transformer is applied to each element before reduction.
+ *
+ * This method provides access to the backing {@link CaseInsensitiveMap} implementation,
+ * allowing advanced operations and inspections. The returned map maintains the same
+ * case-insensitive semantics as this set.
+ *
+ * Warning: Modifying the returned map directly may affect this set's state.
+ * Use with caution and prefer the set's public methods when possible.
+ *
+ * This method is deprecated. Use {@link #removeAll(Collection)} instead.
+ *
+ * This method is deprecated. Use {@link #remove(Object)} instead.
+ *
+ * This method is deprecated. Use {@link #addAll(Collection)} instead.
+ *
+ * This method is deprecated. Use {@link #add(Object)} instead.
+ *
+ * Returns a string representation of this set. The string representation consists of a list of
+ * the set's elements in their original case, enclosed in square brackets ({@code "[]"}). For
+ * {@link String} elements, the original case is preserved, even though the set performs
+ * case-insensitive comparisons.
+ *
+ * The order of elements in the string representation matches the iteration order of the backing map.
+ *
+ * {@code ClassUtilities} includes functionalities such as:
+ *
+ * The {@link #computeInheritanceDistance(Class, Class)} method calculates the number of inheritance steps
+ * between two classes or interfaces. If there is no relationship, it returns {@code -1}. This method also
+ * supports primitive widening conversions as defined in JLS 5.1.2, treating widening paths like
+ * byte→short→int→long→float→double as inheritance relationships.
+ *
+ * Includes methods for loading resources from the classpath as strings or byte arrays, throwing appropriate
+ * exceptions if the resource cannot be found or read.
+ *
+ * Detects and supports environments such as OSGi or JPMS for proper class loading. Uses caching
+ * for efficient retrieval of class loaders in these environments.
+ *
+ * Loads a class using the specified ClassLoader, with recursive handling for array types
+ * and primitive arrays.
+ *
+ * @param name the fully qualified class name or array type descriptor
+ * @param classLoader the ClassLoader to use
+ * @return the loaded Class object
+ * @throws ClassNotFoundException if the class cannot be found
+ */
+ private static Class> loadClass(String name, ClassLoader classLoader) throws ClassNotFoundException {
+ // Support Java-style array names like "int[][]" or "java.lang.String[]"
+ if (name.endsWith("]")) {
+ int dims = 0;
+ String base = name;
+ while (base.endsWith("[]")) {
+ dims++;
+ base = base.substring(0, base.length() - 2);
+ }
+ Class> element;
+ // primitives by simple name
+ switch (base) {
+ case "boolean": element = boolean.class; break;
+ case "byte": element = byte.class; break;
+ case "short": element = short.class; break;
+ case "int": element = int.class; break;
+ case "long": element = long.class; break;
+ case "char": element = char.class; break;
+ case "float": element = float.class; break;
+ case "double": element = double.class; break;
+ default:
+ if (SecurityChecker.isSecurityBlockedName(base)) {
+ throw new SecurityException("Class loading denied for security reasons: " + base);
+ }
+ if (classLoader != null) {
+ element = classLoader.loadClass(base);
+ } else {
+ element = Class.forName(base, false, getClassLoader(ClassUtilities.class));
+ }
+ }
+ Class> arrayClass = element;
+ for (int i = 0; i < dims; i++) {
+ arrayClass = Array.newInstance(arrayClass, 0).getClass();
+ }
+ return arrayClass;
+ }
+
+ // Optimized JVM descriptor handling - count brackets once to avoid re-string-bashing
+ if (name.startsWith("[")) {
+ int dims = 0;
+ while (dims < name.length() && name.charAt(dims) == '[') {
+ dims++;
+ }
+
+ if (dims >= name.length()) {
+ throw new ClassNotFoundException("Bad descriptor: " + name);
+ }
+
+ Class> element;
+ char typeChar = name.charAt(dims);
+
+ // Java 8 compatible switch - handle primitive types
+ switch (typeChar) {
+ case 'B': element = byte.class; break;
+ case 'S': element = short.class; break;
+ case 'I': element = int.class; break;
+ case 'J': element = long.class; break;
+ case 'F': element = float.class; break;
+ case 'D': element = double.class; break;
+ case 'Z': element = boolean.class; break;
+ case 'C': element = char.class; break;
+ case 'L':
+ // Object type: extract class name from Lcom/example/Class;
+ if (!name.endsWith(";") || name.length() <= dims + 2) {
+ throw new ClassNotFoundException("Bad descriptor: " + name);
+ }
+ // Convert JVM descriptor format (java/lang/String) to Java format (java.lang.String)
+ String className = name.substring(dims + 1, name.length() - 1).replace('/', '.');
+ if (SecurityChecker.isSecurityBlockedName(className)) {
+ throw new SecurityException("Class loading denied for security reasons: " + className);
+ }
+ if (classLoader != null) {
+ element = classLoader.loadClass(className);
+ } else {
+ // Use the standard classloader resolution which handles OSGi/JPMS properly
+ ClassLoader cl = getClassLoader(ClassUtilities.class);
+ element = Class.forName(className, false, cl);
+ }
+ break;
+ default:
+ throw new ClassNotFoundException("Bad descriptor: " + name);
+ }
+
+ // Build array class with the right number of dimensions
+ Class> arrayClass = element;
+ for (int i = 0; i < dims; i++) {
+ arrayClass = Array.newInstance(arrayClass, 0).getClass();
+ }
+ return arrayClass;
+ }
+
+ // Regular class name (not an array)
+ if (classLoader != null) {
+ return classLoader.loadClass(name);
+ } else {
+ // Use the standard classloader resolution which handles OSGi/JPMS properly
+ ClassLoader cl = getClassLoader(ClassUtilities.class);
+ if (SecurityChecker.isSecurityBlockedName(name)) {
+ throw new SecurityException("Class loading denied for security reasons: " + name);
+ }
+ return Class.forName(name, false, cl);
+ }
+ }
+
+ /**
+ * Determines if a class is declared as final.
+ *
+ * Checks if the class has the {@code final} modifier, indicating that it cannot be subclassed.
+ * Example:
+ * This method is useful for identifying classes that enforce singleton patterns
+ * or utility classes that should not be instantiated.
+ * Example:
+ * If the input class is already a non-primitive type, it is returned unchanged.
+ * For primitive types, returns the corresponding wrapper class (e.g., {@code int.class} → {@code Integer.class}).
+ * Examples: Supported Primitive Types: Examples:
+ * This method checks if there is a primitive-wrapper relationship between two classes.
+ * For example, {@code Integer.class} wraps {@code int.class} and vice versa.
+ * Examples: Supported Wrapper Pairs:
+ * This uses {@link SecurityManager}, which is deprecated in recent JDKs.
+ * When no security manager is present, this method performs no checks.
+ *
+ * This method searches through a map of candidate classes to find the one that is most closely
+ * related to the input class in terms of inheritance distance. The search prioritizes:
+ *
+ * This method is typically used for cache misses when looking up class-specific handlers
+ * or processors.
+ *
+ * @param
+ * The selection logic follows these rules in order:
+ *
+ * This method delegates to {@link #loadResourceAsBytes(String)} which first
+ * attempts to resolve the resource using the current thread's context
+ * {@link ClassLoader} and then falls back to the {@code ClassUtilities}
+ * class loader.
+ *
+ * This method attempts to instantiate a class using the following strategies in order:
+ *
+ * This method attempts to instantiate a class using the following strategies in order:
+ *
+ * This uses {@code ReflectionFactory.newConstructorForSerialization()} — the same mechanism
+ * used by {@code ObjectInputStream} for deserialization. The synthetic constructor runs
+ * {@code Object.
+ * This setting is thread-local and does not affect other threads.
+ *
+ * @param state boolean true = on, false = off (for the current thread only)
+ */
+ public static void setUseUnsafe(boolean state) {
+ if (state) {
+ unsafeDepth.set(unsafeDepth.get() + 1);
+ // Initialize singleton on first enable
+ if (unsafe == null) {
+ synchronized (ClassUtilities.class) {
+ if (unsafe == null) {
+ try {
+ unsafe = new Unsafe();
+ } catch (Exception e) {
+ // Failed to initialize - revert the increment
+ unsafeDepth.set(unsafeDepth.get() - 1);
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.log(Level.FINE, "Failed to initialize ReflectionFactory instantiation: " + e.getMessage());
+ }
+ }
+ }
+ }
+ }
+ } else {
+ int depth = unsafeDepth.get();
+ if (depth > 0) {
+ unsafeDepth.set(depth - 1);
+ }
+ }
+ }
+
+ /**
+ * Cached reference to InaccessibleObjectException class (Java 9+), or null if not available
+ */
+ private static final Class> INACCESSIBLE_OBJECT_EXCEPTION_CLASS;
+
+ static {
+ Class> clazz = null;
+ try {
+ clazz = Class.forName("java.lang.reflect.InaccessibleObjectException");
+ } catch (ClassNotFoundException e) {
+ // Java 8 or earlier - this exception doesn't exist
+ }
+ INACCESSIBLE_OBJECT_EXCEPTION_CLASS = clazz;
+ }
+
+ /**
+ * Logs reflection access issues in a concise, readable format without stack traces.
+ * Useful for expected access failures due to module restrictions or private access.
+ *
+ * @param accessible The field, method, or constructor that couldn't be accessed
+ * @param e The exception that was thrown
+ * @param operation Description of what was being attempted (e.g., "read field", "invoke method")
+ */
+ public static void logAccessIssue(AccessibleObject accessible, Exception e, String operation) {
+ if (!LOG.isLoggable(Level.FINEST)) {
+ return;
+ }
+
+ String elementType;
+ String elementName;
+ String declaringClass;
+ String modifiers;
+
+ if (accessible instanceof Field) {
+ Field field = (Field) accessible;
+ elementType = "field";
+ elementName = field.getName();
+ declaringClass = field.getDeclaringClass().getName();
+ modifiers = Modifier.toString(field.getModifiers());
+ } else if (accessible instanceof Method) {
+ Method method = (Method) accessible;
+ elementType = "method";
+ elementName = method.getName() + "()";
+ declaringClass = method.getDeclaringClass().getName();
+ modifiers = Modifier.toString(method.getModifiers());
+ } else if (accessible instanceof Constructor) {
+ Constructor> constructor = (Constructor>) accessible;
+ elementType = "constructor";
+ elementName = constructor.getDeclaringClass().getSimpleName() + "()";
+ declaringClass = constructor.getDeclaringClass().getName();
+ modifiers = Modifier.toString(constructor.getModifiers());
+ } else {
+ elementType = "member";
+ elementName = accessible.toString();
+ declaringClass = "unknown";
+ modifiers = "";
+ }
+
+ // Determine the reason for the access failure
+ String reason = null;
+ if (e instanceof IllegalAccessException) {
+ String msg = e.getMessage();
+ if (msg != null) {
+ if (msg.contains("module")) {
+ reason = "Java module system restriction";
+ } else if (msg.contains("private")) {
+ reason = "private access";
+ } else if (msg.contains("protected")) {
+ reason = "protected access";
+ } else if (msg.contains("package")) {
+ reason = "package-private access";
+ }
+ }
+ } else if (INACCESSIBLE_OBJECT_EXCEPTION_CLASS != null &&
+ INACCESSIBLE_OBJECT_EXCEPTION_CLASS.isInstance(e)) {
+ reason = "Java module system restriction (InaccessibleObjectException)";
+ } else if (e instanceof SecurityException) {
+ reason = "Security manager restriction";
+ }
+
+ if (reason == null) {
+ reason = e.getClass().getSimpleName();
+ }
+
+ // Log the concise message
+ if (LOG.isLoggable(Level.FINEST)) {
+ if (operation != null && !operation.isEmpty()) {
+ LOG.log(Level.FINEST, "Cannot {0} {1} {2} ''{3}'' on {4} ({5})",
+ new Object[]{operation, modifiers, elementType, elementName, declaringClass, reason});
+ } else {
+ LOG.log(Level.FINEST, "Cannot access {0} {1} ''{2}'' on {3} ({4})",
+ new Object[]{modifiers, elementType, elementName, declaringClass, reason});
+ }
+ }
+ }
+
+ /**
+ * Security: Validate and normalize resource path to prevent path traversal attacks.
+ *
+ * @param resourceName The resource name to validate
+ * @return The normalized resource path (with backslashes converted to forward slashes)
+ * @throws SecurityException if the resource path is potentially dangerous
+ */
+ private static String validateAndNormalizeResourcePath(String resourceName) {
+ if (StringUtilities.isEmpty(resourceName)) {
+ throw new SecurityException("Resource name cannot be null or empty");
+ }
+
+ // Security: Block null bytes which can truncate paths
+ if (resourceName.indexOf('\0') >= 0) {
+ throw new SecurityException("Invalid resource path contains null byte: " + resourceName);
+ }
+
+ // Security: Block percent-encoded traversal sequences before normalization
+ // Check for %2e%2e (percent-encoded ..) and %2e%2E and other case variations
+ String lowerPath = resourceName.toLowerCase(Locale.ROOT);
+ if (lowerPath.contains("%2e%2e") || lowerPath.contains("%252e") ||
+ lowerPath.contains("%2e.") || lowerPath.contains(".%2e")) {
+ throw new SecurityException("Invalid resource path contains encoded traversal sequence: " + resourceName);
+ }
+
+ // Normalize backslashes to forward slashes for Windows developers
+ // This is safe because JAR resources always use forward slashes
+ String normalizedPath = resourceName.replace('\\', '/');
+
+ // Security: Block absolute Windows drive paths (e.g., "C:/...", "D:/...")
+ // and UNC paths (e.g., "//server/share/...")
+ // These should never appear in classpath resource lookups
+ final int pathLength = normalizedPath.length();
+
+ // Check for Windows absolute path (e.g., "C:/...")
+ if (pathLength >= 3 && Character.isLetter(normalizedPath.charAt(0))
+ && normalizedPath.charAt(1) == ':' && normalizedPath.charAt(2) == '/') {
+ throw new SecurityException("Absolute/UNC paths not allowed: " + resourceName);
+ }
+
+ // Check for UNC path (e.g., "//server/share/...")
+ if (pathLength >= 2 && normalizedPath.charAt(0) == '/' && normalizedPath.charAt(1) == '/') {
+ throw new SecurityException("Absolute/UNC paths not allowed: " + resourceName);
+ }
+
+ // Security: Block ".." path segments (not just substring) to prevent traversal.
+ // Single-pass scan avoids split() allocation on a hot path.
+ int segmentStart = 0;
+ for (int i = 0; i <= pathLength; i++) {
+ if (i == pathLength || normalizedPath.charAt(i) == '/') {
+ if (i - segmentStart == 2 &&
+ normalizedPath.charAt(segmentStart) == '.' &&
+ normalizedPath.charAt(segmentStart + 1) == '.') {
+ throw new SecurityException("Invalid resource path contains directory traversal: " + resourceName);
+ }
+ segmentStart = i + 1;
+ }
+ }
+
+ // Security: Limit resource name length to prevent DoS
+ // Check the normalized path length to ensure validation happens after normalization
+ int maxLength = getMaxResourceNameLength();
+ if (normalizedPath.length() > maxLength) {
+ throw new SecurityException("Resource name too long (max " + maxLength + "): " + normalizedPath.length());
+ }
+
+ return normalizedPath;
+ }
+
+ private static void verifyClassAndArrayComponent(Class> clazz) {
+ Class> type = clazz;
+ while (type.isArray()) {
+ type = type.getComponentType();
+ }
+ if (!type.isPrimitive()) {
+ SecurityChecker.verifyClass(type);
+ }
+ }
+
+ /**
+ * Convenience method for field access issues
+ */
+ public static void logFieldAccessIssue(Field field, Exception e) {
+ logAccessIssue(field, e, "read");
+ }
+
+ /**
+ * Convenience method for method invocation issues
+ */
+ public static void logMethodAccessIssue(Method method, Exception e) {
+ logAccessIssue(method, e, "invoke");
+ }
+
+ /**
+ * Convenience method for constructor access issues
+ */
+ public static void logConstructorAccessIssue(Constructor> constructor, Exception e) {
+ logAccessIssue(constructor, e, "invoke");
+ }
+
+ /**
+ * Returns all equally "lowest" common supertypes (classes or interfaces) shared by both
+ * {@code classA} and {@code classB}, excluding any types specified in {@code excludeSet}.
+ *
+ * @param classA the first class, may be null
+ * @param classB the second class, may be null
+ * @param excluded a set of classes or interfaces to exclude from the final result
+ * @return a {@code Set} of the most specific common supertypes, excluding any in excluded set
+ */
+ public static Set
+ * This method is a convenience wrapper around
+ * {@link #findLowestCommonSupertypesExcluding(Class, Class, Set)} using a skip list
+ * that includes {@code Object, Serializable, Externalizable, Cloneable}. In other words, if the only common
+ * ancestor is {@code Object.class}, this method returns an empty set.
+ * Example:
+ *
+ * This method should only be used in testing scenarios or when hot-reloading classes.
+ * It clears various internal caches that may hold references to classes and constructors.
+ * Note that ClassValue-backed caches cannot be fully cleared and rely on GC for unused keys.
+ *
+ * ClassValueMap provides significantly faster {@code get()} operations compared to standard
+ * Map implementations:
+ *
+ * The standard {@link #get(Object)} method must accept an {@code Object} and perform a runtime
+ * {@code instanceof Class} guard before routing to the {@link ClassValue} cache (keys that are
+ * not {@code Class} instances fall through to the backing {@link ConcurrentHashMap}). When the
+ * caller already knows the key is a {@code Class}, {@link #getByClass(Class)} skips that guard
+ * entirely and compiles to a near-direct {@link ClassValue#get(Class)} call — a JIT-intrinsified,
+ * identity-based per-{@code Class} load.
+ *
+ * For performance-critical call sites, prefer {@code getByClass(Class)}. To take advantage of it,
+ * hold the field as {@code ClassValueMap
+ * The implementation utilizes Java's {@link ClassValue} mechanism, which is specially optimized
+ * in the JVM through:
+ *
+ * ClassValueMap is designed as a drop-in replacement for existing maps with Class keys:
+ *
+ * ClassValueMap is ideal for:
+ *
+ * The performance benefits come with some trade-offs:
+ *
+ * This implementation is thread-safe for all operations and implements ConcurrentMap.
+ *
+ *
+ * Wrapping this class with standard collection wrappers like {@code Collections.unmodifiableMap()}
+ * or {@code Collections.newSetFromMap()} will destroy the {@code ClassValue} performance benefits.
+ * Always use the raw {@code ClassValueMap} directly or use the provided {@code unmodifiableView()} method
+ * if immutability is required. Note that {@code unmodifiableView()} returns a
+ * {@code Map
+ * Null-safe: a null {@code key} returns the current null-key mapping (or
+ * {@code null} if none), matching the semantics of {@link #get(Object)} with
+ * a {@code null} argument.
+ *
+ * Use this method anywhere the caller has a {@code Class>} in hand —
+ * internal caches, type registries, annotation metadata lookups, conversion
+ * dispatch tables, etc. The saved work is small per call (a {@code GETFIELD}
+ * + {@code CMP} + branch + a cast the JIT doesn't always elide), but adds up
+ * across the millions of calls per second that a library like this sees
+ * through its hot paths.
+ *
+ * @param key the class key, or {@code null}
+ * @return the mapped value, or {@code null} if no mapping exists (or if the
+ * null key has no mapping, for a {@code null} argument)
+ * @see #get(Object)
+ */
+ @SuppressWarnings("unchecked")
+ public V getByClass(Class> key) {
+ if (key == null) {
+ return getNullKeyValue();
+ }
+ Object value = cache.get(key);
+ if (value == NO_VALUE) {
+ return null;
+ }
+ return (V) value;
+ }
+
+ @Override
+ public V put(Class> key, V value) {
+ if (key == null) {
+ Object old = nullKeyStore.getAndSet(maskNullKeyValue(value));
+ return unmaskNullKeyValue(old);
+ }
+ Object old = backingMap.put(key, maskNull(value));
+ cache.remove(key); // Invalidate cached value for this key.
+ return unmaskNull(old);
+ }
+
+ @Override
+ public V remove(Object key) {
+ if (key == null) {
+ Object old = nullKeyStore.getAndSet(NO_NULL_KEY_MAPPING);
+ return unmaskNullKeyValue(old);
+ }
+ // Fast path: identity check is faster than instanceof
+ if (key.getClass() != Class.class) {
+ return null;
+ }
+ Class> clazz = (Class>) key;
+ Object old = backingMap.remove(clazz);
+ if (old != null) {
+ cache.remove(clazz);
+ }
+ return unmaskNull(old);
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ if (key == null) {
+ return hasNullKeyMapping();
+ }
+ // Fast path: identity check is faster than instanceof
+ if (key.getClass() != Class.class) {
+ return false;
+ }
+ return cache.get((Class>) key) != NO_VALUE;
+ }
+
+ @Override
+ public void clear() {
+ // Clear backing stores first.
+ backingMap.clear();
+ nullKeyStore.set(NO_NULL_KEY_MAPPING);
+ // Replace cache instance to atomically invalidate all per-class entries.
+ cache = createCache();
+ }
+
+ @Override
+ public int size() {
+ return backingMap.size() + (hasNullKeyMapping() ? 1 : 0);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return !hasNullKeyMapping() && backingMap.isEmpty();
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ Object nullKeyStored = nullKeyStore.get();
+ if (nullKeyStored != NO_NULL_KEY_MAPPING) {
+ if (java.util.Objects.equals(unmaskNullKeyValue(nullKeyStored), value)) {
+ return true;
+ }
+ }
+ return backingMap.containsValue(value == null ? NULL_VALUE : value);
+ }
+
+ @Override
+ public void forEach(java.util.function.BiConsumer super Class>, ? super V> action) {
+ java.util.Objects.requireNonNull(action);
+ Object nullKeyStored = nullKeyStore.get();
+ if (nullKeyStored != NO_NULL_KEY_MAPPING) {
+ action.accept(null, unmaskNullKeyValue(nullKeyStored));
+ }
+ backingMap.forEach((key, value) -> action.accept(key, unmaskNull(value)));
+ }
+
+ @Override
+ public SetKey Features
+ *
+ *
+ *
+ * Usage Examples
+ * {@code
+ * // Create a case-insensitive set
+ * CaseInsensitiveSet
+ *
+ * Backing Map Selection
+ *
+ *
+ *
+ * Thread Safety
+ *
+ *
+ *
+ * Implementation Note
+ * Deprecated Methods
+ *
+ *
+ *
+ * @param
* Copyright (c) Cedar Software LLC
*
@@ -25,7 +104,7 @@
* 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
+ * License
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -33,172 +112,645 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-public class CaseInsensitiveSet
+ *
+ *
+ *
+ *
+ * Inheritance Distance
+ * Primitive and Wrapper Handling
+ *
+ *
+ *
+ * Resource Loading
+ * OSGi and JPMS ClassLoader Support
+ * Design Notes
+ *
+ *
+ *
+ * Usage Example
+ * {@code
+ * // Compute inheritance distance
+ * int distance = ClassUtilities.computeInheritanceDistance(ArrayList.class, List.class); // Outputs 1
+ *
+ * // Check if a class is primitive
+ * boolean isPrimitive = ClassUtilities.isPrimitive(int.class); // Outputs true
+ *
+ * // Load a resource as a string
+ * String resourceContent = ClassUtilities.loadResourceAsString("example.txt");
+ * }
+ *
+ * @see Class
+ * @see ClassLoader
+ * @see Modifier
+ *
+ * @author John DeRegnaucourt (jdereg@gmail.com)
+ *
+ * Copyright (c) Cedar Software LLC
+ *
+ * Licensed 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
+ *
+ * License
+ *
+ * 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.
+ */
+public class ClassUtilities {
+
+ private static final Logger LOG = Logger.getLogger(ClassUtilities.class.getName());
+ static {
+ LoggingConfig.init();
+ }
+
+ /**
+ * Custom WeakReference that remembers its key name for cleanup via ReferenceQueue
+ */
+ private static final class NamedWeakRef extends WeakReference{@code
+ * boolean isFinal = ClassUtilities.isClassFinal(String.class); // Returns true
+ * boolean notFinal = ClassUtilities.isClassFinal(ArrayList.class); // Returns false
+ * }
+ *
+ * @param c the class to check, must not be null
+ * @return true if the class is final, false otherwise
+ * @throws NullPointerException if the input class is null
+ */
+ public static boolean isClassFinal(Class> c) {
+ return (c.getModifiers() & Modifier.FINAL) != 0;
+ }
+
+ /**
+ * Determines if all constructors in a class are declared as private.
+ * {@code
+ * // Utility class with private constructor
+ * public final class Utils {
+ * private Utils() {}
+ * }
+ *
+ * boolean isPrivate = ClassUtilities.areAllConstructorsPrivate(Utils.class); // Returns true
+ * boolean notPrivate = ClassUtilities.areAllConstructorsPrivate(String.class); // Returns false
+ * }
+ *
+ * @param c the class to check, must not be null
+ * @return true if all constructors in the class are private, false if any constructor is non-private
+ * @throws NullPointerException if the input class is null
+ */
+ public static boolean areAllConstructorsPrivate(Class> c) {
+ Constructor>[] constructors = ReflectionUtils.getAllConstructors(c);
+
+ // If no constructors declared, Java provides implicit public no-arg constructor
+ if (constructors.length == 0) {
+ return false;
+ }
+
+ for (Constructor> constructor : constructors) {
+ if ((constructor.getModifiers() & Modifier.PRIVATE) == 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Converts primitive class to its corresponding wrapper class.
+ * {@code
+ * Class> intWrapper = ClassUtilities.toPrimitiveWrapperClass(int.class); // Returns Integer.class
+ * Class> boolWrapper = ClassUtilities.toPrimitiveWrapperClass(boolean.class); // Returns Boolean.class
+ * Class> sameClass = ClassUtilities.toPrimitiveWrapperClass(String.class); // Returns String.class
+ * }
+ *
+ *
+ *
+ *
+ * @param primitiveClass the class to convert, must not be null
+ * @return the wrapper class if the input is primitive, otherwise the input class itself
+ * @throws IllegalArgumentException if the input class is null or not a recognized primitive type
+ */
+ public static Class> toPrimitiveWrapperClass(Class> primitiveClass) {
+ if (primitiveClass == null) {
+ throw new IllegalArgumentException("primitiveClass cannot be null");
+ }
+
+ if (!primitiveClass.isPrimitive()) {
+ return primitiveClass;
+ }
+
+ Class> c = PRIMITIVE_TO_WRAPPER.getByClass(primitiveClass);
+
+ if (c == null) {
+ throw new IllegalArgumentException("Passed in class: " + primitiveClass + " is not a primitive class");
+ }
+
+ return c;
+ }
+
+ /**
+ * Converts a wrapper class to its corresponding primitive class.
+ * If the passed in class is not a wrapper class, it returns the same class.
+ *
+ * {@code
+ * Class> intPrimitive = ClassUtilities.toPrimitiveClass(Integer.class); // Returns int.class
+ * Class> boolPrimitive = ClassUtilities.toPrimitiveClass(Boolean.class); // Returns boolean.class
+ * Class> sameClass = ClassUtilities.toPrimitiveClass(String.class); // Returns String.class
+ * }
+ *
+ * @param wrapperClass the wrapper class to convert
+ * @return the corresponding primitive class, or the same class if not a wrapper
+ * @throws IllegalArgumentException if the passed in class is null
+ */
+ public static Class> toPrimitiveClass(Class> wrapperClass) {
+ if (wrapperClass == null) {
+ throw new IllegalArgumentException("Passed in class cannot be null");
+ }
+
+ Class> primitive = WRAPPER_TO_PRIMITIVE.getByClass(wrapperClass);
+ return primitive != null ? primitive : wrapperClass;
+ }
+
+ /**
+ * Determines if one class is the wrapper type of the other.
+ * {@code
+ * boolean wraps = ClassUtilities.doesOneWrapTheOther(Integer.class, int.class); // Returns true
+ * boolean wraps2 = ClassUtilities.doesOneWrapTheOther(int.class, Integer.class); // Returns true
+ * boolean noWrap = ClassUtilities.doesOneWrapTheOther(Integer.class, long.class); // Returns false
+ * }
+ *
+ *
+ *
+ *
+ * @param x first class to check
+ * @param y second class to check
+ * @return true if one class is the wrapper of the other, false otherwise.
+ * If either argument is {@code null}, this method returns {@code false}.
+ */
+ public static boolean doesOneWrapTheOther(Class> x, Class> y) {
+ if (x == null || y == null) return false;
+ return wrapperMap.get(x) == y || wrapperMap.get(y) == x;
+ }
+
+ /**
+ * Obtains the appropriate ClassLoader depending on whether the environment is OSGi, JPMS, or neither.
+ *
+ * @return the appropriate ClassLoader
+ */
+ public static ClassLoader getClassLoader() {
+ return getClassLoader(ClassUtilities.class);
+ }
+
+ /**
+ * Obtains the appropriate ClassLoader depending on whether the environment is OSGi, JPMS, or neither.
+ *
+ * @param anchorClass the class to use as reference for loading
+ * @return the appropriate ClassLoader
+ */
+ public static ClassLoader getClassLoader(final Class> anchorClass) {
+ if (anchorClass == null) {
+ throw new IllegalArgumentException("Anchor class cannot be null");
+ }
+
+ checkSecurityAccess();
+
+ // Try context class loader first (may have OSGi classes in some containers)
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ if (cl != null) {
+ return cl;
+ }
+
+ // Try anchor class loader
+ cl = anchorClass.getClassLoader();
+ if (cl != null) {
+ return cl;
+ }
+
+ // Try OSGi if available
+ cl = getOSGiClassLoader(anchorClass);
+ if (cl != null) {
+ return cl;
+ }
+
+ // Last resort
+ return SYSTEM_LOADER;
+ }
+
+ /**
+ * Checks if the current security manager allows class loader access.
+ *
+ *
+ *
+ *
+ *
+ * @param newClass the candidate class being evaluated (must not be null)
+ * @param currentClass the current closest matching class (may be null)
+ * @return true if newClass should be preferred over currentClass, false otherwise
+ */
+ private static boolean shouldPreferNewCandidate(Class> newClass, Class> currentClass) {
+ if (currentClass == null) return true;
+ // Prefer classes to interfaces
+ if (newClass.isInterface() != currentClass.isInterface()) {
+ return !newClass.isInterface();
+ }
+ // Prefer the more specific class: newClass should be a subtype of currentClass
+ return currentClass.isAssignableFrom(newClass);
+ }
+
+ /**
+ * Loads resource content as a {@link String}.
+ *
+ *
+ *
+ * @param resourceName Name of the resource file.
+ * @return Content of the resource file as a byte[].
+ * @throws IllegalArgumentException if the resource cannot be found
+ * @throws UncheckedIOException if there is an error reading the resource
+ * @throws NullPointerException if resourceName is null
+ */
+ public static byte[] loadResourceAsBytes(String resourceName) {
+ Objects.requireNonNull(resourceName, "resourceName cannot be null");
+
+ // Security: Validate and normalize resource path to prevent path traversal attacks
+ resourceName = validateAndNormalizeResourcePath(resourceName);
+
+ InputStream inputStream = null;
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader fallbackLoader = ClassUtilities.class.getClassLoader();
+ if (fallbackLoader == null) {
+ fallbackLoader = SYSTEM_LOADER;
+ }
+
+ if (contextLoader != null) {
+ inputStream = contextLoader.getResourceAsStream(resourceName);
+ }
+ if (inputStream == null && fallbackLoader != null && fallbackLoader != contextLoader) {
+ inputStream = fallbackLoader.getResourceAsStream(resourceName);
+ }
+
+ // ClassLoader.getResourceAsStream() doesn't handle leading slashes,
+ // but Class.getResourceAsStream() does. Try without leading slash.
+ if (inputStream == null && resourceName.startsWith("/")) {
+ String noSlash = resourceName.substring(1);
+ if (contextLoader != null) {
+ inputStream = contextLoader.getResourceAsStream(noSlash);
+ }
+ if (inputStream == null && fallbackLoader != null && fallbackLoader != contextLoader) {
+ inputStream = fallbackLoader.getResourceAsStream(noSlash);
+ }
+ }
+
+ if (inputStream == null) {
+ throw new IllegalArgumentException("Resource not found: " + resourceName);
+ }
+
+ try (InputStream in = inputStream) {
+ return readInputStreamFully(in);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Error reading resource: " + resourceName, e);
+ }
+ }
+
+ private static final int BUFFER_SIZE = 65536;
+
+ /**
+ * Reads an InputStream fully and returns its content as a byte array.
+ *
+ * @param inputStream InputStream to read.
+ * @return Content of the InputStream as byte array.
+ * @throws IOException if an I/O error occurs.
+ */
+ private static byte[] readInputStreamFully(InputStream inputStream) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream(BUFFER_SIZE);
+ byte[] data = new byte[BUFFER_SIZE];
+ int nRead;
+ while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
+ buffer.write(data, 0, nRead);
+ }
+ // ByteArrayOutputStream.flush() is a no-op, removed unnecessary call
+ return buffer.toByteArray();
+ }
+
+ private static Object getArgForType(com.cedarsoftware.util.convert.Converter converter, Class> argType) {
+ // Only provide default values for actual primitives, not wrapper types
+ // This avoids masking bugs where null wrapper values are silently converted to 0/false
+ if (argType.isPrimitive()) {
+ return converter.convert(null, argType); // Get the defaults (false, 0, 0.0d, etc.)
+ }
+
+ Supplier
+ *
+ *
+ * @param c Class to instantiate
+ * @param arguments Can be:
+ * - null or empty (no-arg constructor)
+ * - {@code Map
+ *
+ *
+ * @param converter Converter instance used to convert null values to appropriate defaults for primitive types
+ * @param c Class to instantiate
+ * @param arguments Can be:
+ * - null or empty (no-arg constructor)
+ * - {@code Map{@code
+ * Set
+ *
+ * @param classA the first class, may be null
+ * @param classB the second class, may be null
+ * @return a {@code Set} of all equally "lowest" common supertypes, excluding
+ * {@code Object, Serializable, Externalizable, Cloneable}; or an empty
+ * set if none are found beyond {@code Object} (or if either input is null)
+ * @see #findLowestCommonSupertypesExcluding(Class, Class, Set)
+ */
+ public static SetPerformance Advantages
+ *
+ *
+ *
+ * Typed fast path: {@link #getByClass(Class)}
+ * How It Works
+ *
+ *
+ *
+ * Drop-in Replacement
+ *
+ *
+ *
+ * Ideal Use Cases
+ *
+ *
+ *
+ * Trade-offs
+ *
+ *
+ *
+ * Thread Safety
+ * Usage Example
+ * {@code
+ * // Create a registry of class handlers
+ * ClassValueMap
+ *
+ * Important Performance Warning
+ *
+ * Copyright (c) Cedar Software LLC
+ *
+ * Licensed 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
+ *
+ * License
+ *
+ * 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.
+ */
+public class ClassValueMap