From 6c1494a04633402f833df31955e629f36de933c3 Mon Sep 17 00:00:00 2001
From: John DeRegnaucourt
Date: Tue, 24 Dec 2024 15:07:47 -0500
Subject: [PATCH] Finally taking shape. More documentation coming, should be
near final version.
---
.../com/cedarsoftware/util/CompactMap.java | 320 +++++++++---------
.../cedarsoftware/util/CompactMapTest.java | 11 +-
2 files changed, 157 insertions(+), 174 deletions(-)
diff --git a/src/main/java/com/cedarsoftware/util/CompactMap.java b/src/main/java/com/cedarsoftware/util/CompactMap.java
index a7f9b6ac..5b3f2c8f 100644
--- a/src/main/java/com/cedarsoftware/util/CompactMap.java
+++ b/src/main/java/com/cedarsoftware/util/CompactMap.java
@@ -13,7 +13,6 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-import java.lang.reflect.Method;
import java.net.URI;
import java.util.AbstractCollection;
import java.util.AbstractMap;
@@ -224,19 +223,17 @@ public CompactMap() {
throw new IllegalStateException("compactSize() must be >= 2");
}
- // Only check configuration for direct subclasses
- if (getClass() != CompactMap.class && !getClass().getName().contains("caseSen_")) {
- Method caseMethod = ReflectionUtils.getMethodAnyAccess(getClass(), "isCaseInsensitive", false);
- boolean isOverridden = caseMethod != null && caseMethod.getDeclaringClass() != CompactMap.class;
+ // Only check direct subclasses, not our generated classes
+ if (getClass() != CompactMap.class && isLegacyConstructed()) {
+ Map map = getNewMap();
+ if (map instanceof SortedMap) {
+ SortedMap,?> sortedMap = (SortedMap,?>)map;
+ Comparator> comparator = sortedMap.comparator();
- if (isOverridden) {
- Map map = getNewMap();
- if (map instanceof SortedMap) {
- Comparator> comparator = ((SortedMap,?>)map).comparator();
- if (comparator == String.CASE_INSENSITIVE_ORDER && !isCaseInsensitive()) {
- throw new IllegalStateException(
- "Configuration mismatch: Map uses case-insensitive comparison but CompactMap is configured as case-sensitive");
- }
+ // Check case sensitivity consistency
+ if (comparator == String.CASE_INSENSITIVE_ORDER && !isCaseInsensitive()) {
+ throw new IllegalStateException(
+ "Inconsistent configuration: Map uses case-insensitive comparison but isCaseInsensitive() returns false");
}
}
}
@@ -303,40 +300,12 @@ private boolean areKeysEqual(Object key, Object aKey) {
/**
* Compares two keys for ordering based on the map's ordering and case sensitivity settings.
*
- *
- * The comparison follows these rules:
- *
- *
If both keys are equal (as determined by {@link #areKeysEqual}), returns {@code 0}.
- *
If both keys are instances of {@link String}:
- *
- *
Uses a case-insensitive comparator if {@link #isCaseInsensitive()} is {@code true}; otherwise, uses case-sensitive comparison.
- *
Reverses the comparator if the map's ordering is set to {@code REVERSE}.
- *
If one keys is String and other is not, compares class names lexicographically to establish a consistent order (honoring {@code REVERSE} if needed).
- *
- *
- *
If both keys implement {@link Comparable} and are of the exact same class:
- *
- *
Compares them using their natural ordering.
- *
Reverses the result if the map's ordering is set to {@code REVERSE}.
- *
- *
- *
If keys are of different classes or do not implement {@link Comparable}:
- *
- *
Handles {@code null} values: {@code null} is considered less than any non-null key.
- *
Compares class names lexicographically to establish a consistent order (honoring {@code REVERSE} if needed)
- *
- *
- *
- *
- *
- *
Note: This method ensures a durable and consistent ordering, even for keys of differing types or non-comparable keys, by falling back to class name comparison.
- *
* @param key1 the first key to compare
* @param key2 the second key to compare
- * @return a negative integer, zero, or a positive integer as {@code key1} is less than, equal to,
- * or greater than {@code key2}
+ * @param forceReverse override to force reverse ordering regardless of map settings
+ * @return a negative integer, zero, or positive integer as key1 is less than, equal to, or greater than key2
*/
- private int compareKeysForOrder(Object key1, Object key2) {
+ private int compareKeysForOrder(Object key1, Object key2, boolean forceReverse) {
// 1. Handle nulls explicitly
if (key1 == null) {
return (key2 == null) ? 0 : 1; // Nulls last when sorting
@@ -350,35 +319,50 @@ private int compareKeysForOrder(Object key1, Object key2) {
return 0;
}
- // 3. Cache ordering and case sensitivity to avoid repeated method calls
- String ordering = getOrdering();
- boolean isReverse = REVERSE.equals(ordering);
-
- // 4. String comparison - most common case
- Class> key1Class = key1.getClass();
- Class> key2Class = key2.getClass();
-
- if (key1Class == String.class) {
- if (key2Class == String.class) {
- int comparison = isCaseInsensitive()
- ? String.CASE_INSENSITIVE_ORDER.compare((String) key1, (String) key2)
- : ((String) key1).compareTo((String) key2);
- return isReverse ? -comparison : comparison;
+ int result;
+ if (isLegacyConstructed()) {
+ if (isCaseInsensitive() && key1 instanceof String && key2 instanceof String) {
+ result = String.CASE_INSENSITIVE_ORDER.compare((String)key1, (String)key2);
+ } else if (key1 instanceof Comparable) {
+ result = ((Comparable)key1).compareTo(key2);
+ } else {
+ result = key1.getClass().getName().compareTo(key2.getClass().getName());
+ }
+ } else {
+ // Non-legacy mode logic
+ Class> key1Class = key1.getClass();
+ Class> key2Class = key2.getClass();
+
+ if (key1Class == String.class) {
+ if (key2Class == String.class) {
+ result = isCaseInsensitive()
+ ? String.CASE_INSENSITIVE_ORDER.compare((String) key1, (String) key2)
+ : ((String) key1).compareTo((String) key2);
+ } else {
+ // key1 is String, key2 is not - use class name comparison
+ result = key1Class.getName().compareTo(key2Class.getName());
+ }
+ } else if (key1Class == key2Class && key1 instanceof Comparable) {
+ // Same type and comparable
+ result = ((Comparable