From 4be373a0b1651af8d0944ab2236f49da9509400a Mon Sep 17 00:00:00 2001 From: John DeRegnaucourt Date: Mon, 20 Jan 2025 15:59:37 -0500 Subject: [PATCH] > * [ClassUtilities](userguide.md#classutilities) adds > * `Set> findLowestCommonSupertypes(Class a, Class b)` > * which returns the lowest common anscestor(s) of two classes, excluding `Object.class.` This is useful for finding the common ancestor of two classes that are not related by inheritance. Generally, executes in O(n log n) - uses sort internally. If more than one exists, you can filter the returned Set as you please, favoring classes, interfaces, etc. > * `Class findLowestCommonSupertype(Class a, Class b)` > * which is a convenience method that calls the above method and then returns the first one in the Set or null. > * `boolean haveCommonAncestor(Class a, Class b)` > * which returns true if the two classes have a common ancestor (excluding `Object.class`). > * `Set> getAllSupertypes(Class clazz)` > * which returns all superclasses and interfaces of a class, including itself. This is useful for finding all the classes and interfaces that a class implements or extends. > * Moved `Sealable*` test cases to json-io project. > * Removed remaining usages of deprecated `CompactLinkedMap.` --- README.md | 4 +- changelog.md | 12 + pom.xml | 4 +- .../cedarsoftware/util/ClassUtilities.java | 198 +++++++++++++-- .../cedarsoftware/util/convert/Converter.java | 9 +- .../util/convert/TimestampConversions.java | 5 +- .../util/ClassUtilitiesTest.java | 237 +++++++++++++++++- .../cedarsoftware/util/CompactMapTest.java | 2 +- .../cedarsoftware/util/SealableListTest.java | 204 --------------- .../cedarsoftware/util/SealableMapTest.java | 176 ------------- .../util/SealableNavigableMapSubsetTest.java | 101 -------- .../util/SealableNavigableMapTest.java | 148 ----------- .../util/SealableNavigableSetTest.java | 128 ---------- .../util/SealableNavigableSubsetTest.java | 103 -------- .../cedarsoftware/util/SealableSetTest.java | 227 ----------------- .../util/convert/ConverterEverythingTest.java | 79 +++++- 16 files changed, 523 insertions(+), 1114 deletions(-) delete mode 100644 src/test/java/com/cedarsoftware/util/SealableListTest.java delete mode 100644 src/test/java/com/cedarsoftware/util/SealableMapTest.java delete mode 100644 src/test/java/com/cedarsoftware/util/SealableNavigableMapSubsetTest.java delete mode 100644 src/test/java/com/cedarsoftware/util/SealableNavigableMapTest.java delete mode 100644 src/test/java/com/cedarsoftware/util/SealableNavigableSetTest.java delete mode 100644 src/test/java/com/cedarsoftware/util/SealableNavigableSubsetTest.java delete mode 100644 src/test/java/com/cedarsoftware/util/SealableSetTest.java diff --git a/README.md b/README.md index 607ce4c48..17620ac93 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Both of these features ensure that our library can be seamlessly integrated into To include in your project: ##### Gradle ```groovy -implementation 'com.cedarsoftware:java-util:3.0.0' +implementation 'com.cedarsoftware:java-util:3.0.1' ``` ##### Maven @@ -40,7 +40,7 @@ implementation 'com.cedarsoftware:java-util:3.0.0' com.cedarsoftware java-util - 3.0.0 + 3.0.1 ``` --- diff --git a/changelog.md b/changelog.md index bf6f192b5..3d43f42df 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,16 @@ ### Revision History +#### 3.0.1 +> * [ClassUtilities](userguide.md#classutilities) adds +> * `Set> findLowestCommonSupertypes(Class a, Class b)` +> * which returns the lowest common anscestor(s) of two classes, excluding `Object.class.` This is useful for finding the common ancestor of two classes that are not related by inheritance. Generally, executes in O(n log n) - uses sort internally. If more than one exists, you can filter the returned Set as you please, favoring classes, interfaces, etc. +> * `Class findLowestCommonSupertype(Class a, Class b)` +> * which is a convenience method that calls the above method and then returns the first one in the Set or null. +> * `boolean haveCommonAncestor(Class a, Class b)` +> * which returns true if the two classes have a common ancestor (excluding `Object.class`). +> * `Set> getAllSupertypes(Class clazz)` +> * which returns all superclasses and interfaces of a class, including itself. This is useful for finding all the classes and interfaces that a class implements or extends. +> * Moved `Sealable*` test cases to json-io project. +> * Removed remaining usages of deprecated `CompactLinkedMap.` #### 3.0.0 > * [DeepEquals](userguide.md#deepequals) now outputs the first encountered graph "diff" in the passed in input/output options Map if provided. See userguide for example output. > * [CompactMap](userguide.md#compactmap) and [CompactSet](userguide.md#compactset) no longer do you need to sublcass for variations. Use the new builder api. diff --git a/pom.xml b/pom.xml index 429f47ae4..0b95e6ddc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.cedarsoftware java-util bundle - 3.0.0 + 3.0.1 Java Utilities https://github.com/jdereg/java-util @@ -32,7 +32,7 @@ 5.11.4 4.11.0 3.27.2 - 4.30.0 + 4.32.0 1.22.0 diff --git a/src/main/java/com/cedarsoftware/util/ClassUtilities.java b/src/main/java/com/cedarsoftware/util/ClassUtilities.java index 5074fe085..0d3e30c93 100644 --- a/src/main/java/com/cedarsoftware/util/ClassUtilities.java +++ b/src/main/java/com/cedarsoftware/util/ClassUtilities.java @@ -25,6 +25,7 @@ import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; @@ -1145,19 +1146,6 @@ private String buildParameterTypeString(Constructor constructor) { } } - private static String createCacheKey(Class c, Collection args) { - StringBuilder s = new StringBuilder(c.getName()); - for (Object o : args) { - if (o == null) { - s.append(":null"); - } else { - s.append(':'); - s.append(o.getClass().getSimpleName()); - } - } - return s.toString(); - } - /** * Determines if a class is an enum or is related to an enum through inheritance or enclosure. *

@@ -1371,4 +1359,186 @@ public static void setUseUnsafe(boolean state) { } } } -} + + /** + * Returns all equally "lowest" common supertypes (classes or interfaces) shared by both + * {@code classA} and {@code classB}, excluding any types specified in {@code skipList}. + *

+ * A "lowest" common supertype is defined as any type {@code T} such that: + *

    + *
  • {@code T} is a supertype of both {@code classA} and {@code classB} (either + * via inheritance or interface implementation), and
  • + *
  • {@code T} is not a superclass (or superinterface) of any other common supertype + * in the result. In other words, no other returned type is a subtype of {@code T}.
  • + *
+ * + *

Typically, this method is used to discover the most specific shared classes or + * interfaces without including certain unwanted types—such as {@code Object.class} + * or other "marker" interfaces (e.g. {@code Serializable}, {@code Cloneable}, + * {@code Comparable}). If you do not want these in the final result, add them to + * {@code skipList}.

+ * + *

The returned set may contain multiple types if they are "equally specific" + * and do not extend or implement one another. If the resulting set is empty, + * then there is no common supertype of {@code classA} and {@code classB} outside + * of the skipped types.

+ * + *

Example (skipping {@code Object.class}): + *

{@code
+     * Set> skip = Collections.singleton(Object.class);
+     * Set> supertypes = findLowestCommonSupertypesWithSkip(TreeSet.class, HashSet.class, skip);
+     * // supertypes might contain only [Set], since Set is the lowest interface
+     * // they both implement, and we skipped Object.
+     * }
+ * + * @param classA the first class, may be null + * @param classB the second class, may be null + * @param skipList a set of classes or interfaces to exclude from the final result + * (e.g. {@code Object.class}, {@code Serializable.class}, etc.). + * May be empty but not null. + * @return a {@code Set} of the most specific common supertypes of {@code classA} + * and {@code classB}, excluding any in {@code skipList}; if either class + * is {@code null} or the entire set is excluded, an empty set is returned + * @see #findLowestCommonSupertypes(Class, Class) + * @see #getAllSupertypes(Class) + */ + public static Set> findLowestCommonSupertypesExcluding( + Class classA, Class classB, + Set> skipList) + { + if (classA == null || classB == null) { + return Collections.emptySet(); + } + if (classA.equals(classB)) { + // If it's in the skip list, return empty; otherwise return singleton + return skipList.contains(classA) ? Collections.emptySet() + : Collections.singleton(classA); + } + + // 1) Gather all supertypes of A and B + Set> allA = getAllSupertypes(classA); + Set> allB = getAllSupertypes(classB); + + // 2) Intersect + allA.retainAll(allB); + + // 3) Remove anything in the skip list, including Object if you like + allA.removeAll(skipList); + + if (allA.isEmpty()) { + return Collections.emptySet(); + } + + // 4) Sort by descending depth + List> candidates = new ArrayList<>(allA); + candidates.sort((x, y) -> { + int dx = getDepth(x); + int dy = getDepth(y); + return Integer.compare(dy, dx); // descending + }); + + // 5) Identify "lowest" + Set> lowest = new LinkedHashSet<>(); + Set> unionOfAncestors = new HashSet<>(); + + for (Class T : candidates) { + if (unionOfAncestors.contains(T)) { + // T is an ancestor of something in 'lowest' + continue; + } + // T is indeed a "lowest" so far + lowest.add(T); + + // Add all T's supertypes to the union set + Set> ancestorsOfT = getAllSupertypes(T); + unionOfAncestors.addAll(ancestorsOfT); + } + + return lowest; + } + + /** + * Returns all equally "lowest" common supertypes (classes or interfaces) that + * both {@code classA} and {@code classB} share, automatically excluding + * {@code Object.class}. + *

+ * This method is a convenience wrapper around + * {@link #findLowestCommonSupertypesExcluding(Class, Class, Set)} using a skip list + * that includes only {@code Object.class}. In other words, if the only common + * ancestor is {@code Object.class}, this method returns an empty set. + *

+ * + *

Example: + *

{@code
+     * Set> supertypes = findLowestCommonSupertypes(Integer.class, Double.class);
+     * // Potentially returns [Number, Comparable, Serializable] because those are
+     * // equally specific and not ancestors of one another, ignoring Object.class.
+     * }
+ * + * @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.class}; or an empty set if none are found beyond {@code Object} + * (or if either input is null) + * @see #findLowestCommonSupertypesExcluding(Class, Class, Set) + * @see #getAllSupertypes(Class) + */ + public static Set> findLowestCommonSupertypes(Class classA, Class classB) { + return findLowestCommonSupertypesExcluding(classA, classB, + CollectionUtilities.setOf(Object.class)); + } + + /** + * Returns the *single* most specific type from findLowestCommonSupertypes(...). + * If there's more than one, returns any one (or null if none). + */ + public static Class findLowestCommonSupertype(Class classA, Class classB) { + Set> all = findLowestCommonSupertypes(classA, classB); + return all.isEmpty() ? null : all.iterator().next(); + } + + /** + * Gather all superclasses and all interfaces (recursively) of 'clazz', + * including clazz itself. + *

+ * BFS or DFS is fine. Here is a simple BFS approach: + */ + public static Set> getAllSupertypes(Class clazz) { + Set> results = new HashSet<>(); + Queue> queue = new ArrayDeque<>(); + queue.add(clazz); + while (!queue.isEmpty()) { + Class current = queue.poll(); + if (current != null && results.add(current)) { + // Add its superclass + Class sup = current.getSuperclass(); + if (sup != null) { + queue.add(sup); + } + // Add all interfaces + for (Class ifc : current.getInterfaces()) { + queue.add(ifc); + } + } + } + return results; + } + + /** + * Returns distance of 'clazz' from Object.class in its *class* hierarchy + * (not counting interfaces). This is a convenience for sorting by depth. + */ + private static int getDepth(Class clazz) { + int depth = 0; + while (clazz != null) { + clazz = clazz.getSuperclass(); + depth++; + } + return depth; + } + + // Convenience boolean method + public static boolean haveCommonAncestor(Class a, Class b) { + return !findLowestCommonSupertypes(a, b).isEmpty(); + } +} \ No newline at end of file diff --git a/src/main/java/com/cedarsoftware/util/convert/Converter.java b/src/main/java/com/cedarsoftware/util/convert/Converter.java index 8454c307f..e16be9af4 100644 --- a/src/main/java/com/cedarsoftware/util/convert/Converter.java +++ b/src/main/java/com/cedarsoftware/util/convert/Converter.java @@ -1579,9 +1579,9 @@ public boolean isCollectionConversionSupported(Class sourceType, Class tar return target.isArray() || Collection.class.isAssignableFrom(target); } - // If the source is a generic Collection, we only support converting it to an array type. + // If the source is a generic Collection, we only support converting it to an array or collection if (Collection.class.isAssignableFrom(sourceType)) { - return target.isArray(); + return target.isArray() || Collection.class.isAssignableFrom(target); } // If the source is an array: @@ -1640,6 +1640,11 @@ public boolean isSimpleTypeConversionSupported(Class source, Class target) return false; } + // Special case: Number.class as source + if (source.equals(Number.class)) { + return isConversionInMap(Long.class, target); + } + // Direct conversion check first (fastest) if (isConversionInMap(source, target)) { return true; diff --git a/src/main/java/com/cedarsoftware/util/convert/TimestampConversions.java b/src/main/java/com/cedarsoftware/util/convert/TimestampConversions.java index f341154c9..6e5fa2c80 100644 --- a/src/main/java/com/cedarsoftware/util/convert/TimestampConversions.java +++ b/src/main/java/com/cedarsoftware/util/convert/TimestampConversions.java @@ -46,8 +46,9 @@ static BigDecimal toBigDecimal(Object from, Converter converter) { } static BigInteger toBigInteger(Object from, Converter converter) { - Duration duration = toDuration(from, converter); - return DurationConversions.toBigInteger(duration, converter); + Timestamp timestamp = (Timestamp) from; + Instant instant = timestamp.toInstant(); + return InstantConversions.toBigInteger(instant, converter); } static LocalDateTime toLocalDateTime(Object from, Converter converter) { diff --git a/src/test/java/com/cedarsoftware/util/ClassUtilitiesTest.java b/src/test/java/com/cedarsoftware/util/ClassUtilitiesTest.java index 845f94a4d..ed47d63e7 100644 --- a/src/test/java/com/cedarsoftware/util/ClassUtilitiesTest.java +++ b/src/test/java/com/cedarsoftware/util/ClassUtilitiesTest.java @@ -1,8 +1,11 @@ package com.cedarsoftware.util; +import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.AbstractList; +import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -10,7 +13,11 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.stream.Stream; import com.cedarsoftware.util.convert.Converter; @@ -27,6 +34,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -332,7 +340,7 @@ public void testInterfaceToInterfaceNoInheritance() { } @Test - public void testSameClass() { + public void testSameClass2() { assertEquals(0, ClassUtilities.computeInheritanceDistance(TestClass.class, TestClass.class), "Distance from a class to itself should be 0."); } @@ -464,4 +472,231 @@ protected Class findClass(String className) private final String alternateName; private final Class clazz; } + + // ------------------------------------------------------------------ + // 1) findLowestCommonSupertypes() Tests + // ------------------------------------------------------------------ + + /** + * If both classes are the same, the only "lowest" common supertype + * should be that class itself. + */ + @Test + void testSameClass() + { + Set> result = ClassUtilities.findLowestCommonSupertypes(String.class, String.class); + assertEquals(1, result.size()); + assertTrue(result.contains(String.class)); + } + + /** + * If one class is a direct subclass of the other, then the parent class + * (or interface) is the only common supertype (besides Object). + * Here, TreeSet is a subclass of AbstractSet->AbstractCollection->Object + * and it implements NavigableSet->SortedSet->Set->Collection->Iterable. + * But NavigableSet, SortedSet, and Set are also supertypes of TreeSet. + */ + @Test + void testSubClassCase() + { + // TreeSet vs. SortedSet + // SortedSet is an interface that TreeSet implements directly, + // so both share SortedSet as a common supertype, but let's see how "lowest" is chosen. + Set> result = ClassUtilities.findLowestCommonSupertypes(TreeSet.class, SortedSet.class); + // The BFS for TreeSet includes: [TreeSet, AbstractSet, AbstractCollection, Object, + // NavigableSet, SortedSet, Set, Collection, Iterable, ...] + // For SortedSet: [SortedSet, Set, Collection, Iterable, ...] (plus possibly Object if you include it). + // + // The intersection (excluding Object) is {TreeSet, NavigableSet, SortedSet, Set, Collection, Iterable} + // But only "SortedSet" is a supertype of both. Actually, "NavigableSet" is also present, but SortedSet + // is an ancestor of NavigableSet. For direct class vs interface, here's the tricky part: + // - SortedSet.isAssignableFrom(TreeSet) = true + // - NavigableSet.isAssignableFrom(TreeSet) = true (meaning NavigableSet is also a parent) + // - NavigableSet extends SortedSet -> so SortedSet is higher than NavigableSet. + // Since we want "lowest" (i.e. the most specific supertypes), NavigableSet is a child of SortedSet. + // That means SortedSet is "more general," so it would be excluded if NavigableSet is in the set. + // The final set might end up with [TreeSet, NavigableSet] or possibly just [NavigableSet] (depending + // on the BFS order). + // + // However, because one of our classes *is* SortedSet, that means SortedSet must be a common supertype + // of itself. Meanwhile, NavigableSet is a sub-interface of SortedSet. So the more specific supertype + // is NavigableSet. But is SortedSet an ancestor of NavigableSet? Yes => that means we'd remove SortedSet + // if NavigableSet is in the intersection. But we also have the actual class TreeSet. Is that a supertype + // of SortedSet or vice versa? Actually, SortedSet is an interface that TreeSet implements, so SortedSet + // is an ancestor of TreeSet. The "lowest" common supertype is the one that is *not* an ancestor + // of anything else in the intersection. + // + // In typical BFS logic, we would likely end up with a result = {TreeSet} if we consider a class a + // valid "supertype" of itself or {NavigableSet} if we consider the interface to be a lower child than + // SortedSet. In many real uses, though, we want to see "NavigableSet" or "SortedSet" as the result + // because the interface is the "lowest" that both share. Let's just check the actual outcome: + // + // The main point: The method will return *something* that proves they're related. We'll just verify + // that we don't end up with an empty set. + assertFalse(result.isEmpty(), "They should share at least a common interface"); + } + + /** + * Two sibling classes that share a mid-level abstract parent, plus + * a common interface. For example, ArrayList vs. LinkedList both implement + * List. The "lowest" common supertype is List (not Collection or Iterable). + */ + @Test + void testTwoSiblingsSharingInterface() + { + // ArrayList and LinkedList share: List, AbstractList, Collection, Iterable, etc. + // The "lowest" or most specific common supertype should be "List". + Set> result = ClassUtilities.findLowestCommonSupertypes(ArrayList.class, LinkedList.class); + // We expect at least "List" in the final. + // Because AbstractList is a parent of both, but List is an interface also implemented + // by both. Which is more "specific"? Actually, AbstractList is more specialized than + // List from a class perspective. But from an interface perspective, we might see them + // as both in the intersection. This is exactly why we do a final pass that removes + // anything that is an ancestor. AbstractList is a superclass of ArrayList/LinkedList, + // but it's *not* an ancestor of the interface "List" or vice versa. So we might end up + // with multiple. Typically, though, "List" is not an ancestor of "AbstractList" or + // vice versa. So the final set might contain both AbstractList and List. + // Checking that the set is not empty, and definitely contains "List": + assertFalse(result.isEmpty()); + assertTrue(result.contains(AbstractList.class)); + } + + /** + * Two sibling classes implementing Set, e.g. TreeSet vs HashSet. The + * "lowest" common supertype is Set (not Collection or Iterable). + */ + @Test + void testTreeSetVsHashSet() + { + Set> result = ClassUtilities.findLowestCommonSupertypes(TreeSet.class, HashSet.class); + // We know from typical usage this intersection should definitely include Set, possibly + // also NavigableSet for the TreeSet side, but HashSet does not implement NavigableSet. + // So the final "lowest" is likely just Set. Let's verify it contains Set. + assertFalse(result.isEmpty()); + assertTrue(result.contains(AbstractSet.class)); + } + + /** + * Classes from different hierarchies that share multiple interfaces: e.g. Integer vs. Double, + * both extend Number but also implement Serializable and Comparable. Because neither + * interface is an ancestor of the other, we may get multiple "lowest" supertypes: + * {Number, Comparable, Serializable}. + */ + @Test + void testIntegerVsDouble() + { + Set> result = ClassUtilities.findLowestCommonSupertypes(Integer.class, Double.class); + // Expect something like {Number, Comparable, Serializable} all to appear, + // because: + // - Number is a shared *class* parent. + // - They both implement Comparable (erasure: Comparable). + // - They also implement Serializable. + // None of these is an ancestor of the other, so we might see all three. + assertFalse(result.isEmpty()); + assertTrue(result.contains(Number.class), "Should contain Number"); + assertTrue(result.contains(Comparable.class), "Should contain Comparable"); + } + + /** + * If two classes have no relationship except Object, then after removing Object we get an empty set. + */ + @Test + void testNoCommonAncestor() + { + // Example: Runnable is an interface, and Error is a class that does not implement Runnable. + Set> result = ClassUtilities.findLowestCommonSupertypes(Runnable.class, Error.class); + // Intersection is effectively just Object, which we exclude. So empty set: + assertTrue(result.isEmpty(), "No supertypes more specific than Object"); + } + + /** + * If either input is null, we return empty set. + */ + @Test + void testNullInput() + { + assertTrue(ClassUtilities.findLowestCommonSupertypes(null, String.class).isEmpty()); + assertTrue(ClassUtilities.findLowestCommonSupertypes(String.class, null).isEmpty()); + } + + /** + * Interface vs. a class that implements it: e.g. Runnable vs. Thread. + * Thread implements Runnable, so the intersection includes Runnable and Thread. + * But we only want the "lowest" supertype(s). Because Runnable is + * an ancestor of Thread, we typically keep Thread in the set. However, + * if your BFS is strictly for "common *super*types," you might see that + * from the perspective of the interface, Thread is not in the BFS. So + * let's see how your final algorithm handles it. Usually, you'd get {Runnable} + * or possibly both. We'll at least test that it's not empty. + */ + @Test + void testInterfaceVsImpl() + { + Set> result = ClassUtilities.findLowestCommonSupertypes(Runnable.class, Thread.class); + // Usually we'd see {Runnable}, because "Runnable" is a supertype of "Thread". + // "Thread" is not a supertype of "Runnable," so it doesn't appear in the intersection set + // if we do a standard BFS from each side. + // Just check for non-empty: + assertFalse(result.isEmpty()); + // And very likely includes Runnable: + assertTrue(result.contains(Runnable.class)); + } + + // ------------------------------------------------------------------ + // 2) findLowestCommonSupertype() Tests + // ------------------------------------------------------------------ + + /** + * For classes that share multiple equally specific supertypes, + * findLowestCommonSupertype() just picks one of them (implementation-defined). + * E.g. Integer vs. Double => it might return Number, or Comparable, or Serializable. + */ + @Test + void testFindLowestCommonSupertype_MultipleEquallySpecific() + { + Class result = ClassUtilities.findLowestCommonSupertype(Integer.class, Double.class); + assertNotNull(result); + // The method chooses *one* of {Number, Comparable, Serializable}. + // We simply check it's one of those three. + Set> valid = CollectionUtilities.setOf(Number.class, Comparable.class, Serializable.class); + assertTrue(valid.contains(result), + "Expected one of " + valid + " but got: " + result); + } + + /** + * If there's no common supertype other than Object, findLowestCommonSupertype() returns null. + */ + @Test + void testFindLowestCommonSupertype_None() + { + Class result = ClassUtilities.findLowestCommonSupertype(Runnable.class, Error.class); + assertNull(result, "No common supertype other than Object => null"); + } + + // ------------------------------------------------------------------ + // 3) haveCommonAncestor() Tests + // ------------------------------------------------------------------ + + @Test + void testHaveCommonAncestor_True() + { + // LinkedList and ArrayList share 'List' + assertTrue(ClassUtilities.haveCommonAncestor(LinkedList.class, ArrayList.class)); + // Integer and Double share 'Number' + assertTrue(ClassUtilities.haveCommonAncestor(Integer.class, Double.class)); + } + + @Test + void testHaveCommonAncestor_False() + { + // Runnable vs. Error => only Object in common + assertFalse(ClassUtilities.haveCommonAncestor(Runnable.class, Error.class)); + } + + @Test + void testHaveCommonAncestor_Null() + { + assertFalse(ClassUtilities.haveCommonAncestor(null, String.class)); + assertFalse(ClassUtilities.haveCommonAncestor(String.class, null)); + } } diff --git a/src/test/java/com/cedarsoftware/util/CompactMapTest.java b/src/test/java/com/cedarsoftware/util/CompactMapTest.java index 33fd9a181..c33b8aa1b 100644 --- a/src/test/java/com/cedarsoftware/util/CompactMapTest.java +++ b/src/test/java/com/cedarsoftware/util/CompactMapTest.java @@ -2629,7 +2629,7 @@ public void testCompactMapSequence() assert linkedMap.containsKey("FoO" + (linkedMap.compactSize() + 3)); assert !linkedMap.containsKey("foo" + (linkedMap.compactSize() + 3)); - CompactMap copy = CompactMap.builder().insertionOrder().build(); + CompactMap copy = CompactMap.builder().sourceMap(linkedMap).insertionOrder().build(); assert copy.equals(linkedMap); assert copy.containsKey("FoO0"); diff --git a/src/test/java/com/cedarsoftware/util/SealableListTest.java b/src/test/java/com/cedarsoftware/util/SealableListTest.java deleted file mode 100644 index 780038fff..000000000 --- a/src/test/java/com/cedarsoftware/util/SealableListTest.java +++ /dev/null @@ -1,204 +0,0 @@ -package com.cedarsoftware.util; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.NoSuchElementException; -import java.util.function.Supplier; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @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. - */ -class SealableListTest { - - private SealableList list; - private volatile boolean sealedState = false; - private Supplier sealedSupplier = () -> sealedState; - - @BeforeEach - void setUp() { - sealedState = false; - list = new SealableList<>(new ArrayList<>(), sealedSupplier); - list.add(10); - list.add(20); - list.add(30); - } - - @Test - void testAdd() { - assertFalse(list.isEmpty()); - assertEquals(3, list.size()); - list.add(40); - assertTrue(list.contains(40)); - assertEquals(4, list.size()); - } - - @Test - void testRemove() { - assertTrue(list.remove(Integer.valueOf(20))); - assertFalse(list.contains(20)); - assertEquals(2, list.size()); - } - - @Test - void testAddWhenSealed() { - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> list.add(50)); - } - - @Test - void testRemoveWhenSealed() { - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> list.remove(Integer.valueOf(10))); - } - - @Test - void testIteratorWhenSealed() { - Iterator it = list.iterator(); - sealedState = true; - assertTrue(it.hasNext()); - assertEquals(10, it.next()); - assertThrows(UnsupportedOperationException.class, it::remove); - } - - @Test - void testListIteratorSetWhenSealed() { - ListIterator it = list.listIterator(); - sealedState = true; - it.next(); - assertThrows(UnsupportedOperationException.class, () -> it.set(100)); - } - - @Test - void testSubList() { - List sublist = list.subList(0, 2); - assertEquals(2, sublist.size()); - assertTrue(sublist.contains(10)); - assertTrue(sublist.contains(20)); - assertFalse(sublist.contains(30)); - sublist.add(25); - assertTrue(sublist.contains(25)); - assertEquals(3, sublist.size()); - } - - @Test - void testSubListWhenSealed() { - List sublist = list.subList(0, 2); - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> sublist.add(35)); - assertThrows(UnsupportedOperationException.class, () -> sublist.remove(Integer.valueOf(10))); - } - - @Test - void testClearWhenSealed() { - sealedState = true; - assertThrows(UnsupportedOperationException.class, list::clear); - } - - @Test - void testSetWhenSealed() { - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> list.set(1, 100)); - } - - @Test - void testListIteratorAddWhenSealed() { - ListIterator it = list.listIterator(); - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> it.add(45)); - } - - @Test - void testAddAllWhenSealed() { - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> list.addAll(Arrays.asList(50, 60))); - } - - @Test - void testIteratorTraversal() { - Iterator it = list.iterator(); - assertTrue(it.hasNext()); - assertEquals(Integer.valueOf(10), it.next()); - assertEquals(Integer.valueOf(20), it.next()); - assertEquals(Integer.valueOf(30), it.next()); - assertFalse(it.hasNext()); - assertThrows(NoSuchElementException.class, it::next); - } - - @Test - void testListIteratorPrevious() { - ListIterator it = list.listIterator(2); - assertEquals(Integer.valueOf(20), it.previous()); - assertTrue(it.hasPrevious()); - - Iterator it2 = list.listIterator(0); - assertEquals(Integer.valueOf(10), it2.next()); - assertEquals(Integer.valueOf(20), it2.next()); - assertEquals(Integer.valueOf(30), it2.next()); - assertThrows(NoSuchElementException.class, () -> it2.next()); - } - - @Test - void testEquals() { - SealableList other = new SealableList<>(sealedSupplier); - other.add(10); - other.add(20); - other.add(30); - assertEquals(list, other); - other.add(40); - assertNotEquals(list, other); - } - - @Test - void testHashCode() { - SealableList other = new SealableList<>(sealedSupplier); - other.add(10); - other.add(20); - other.add(30); - assertEquals(list.hashCode(), other.hashCode()); - other.add(40); - assertNotEquals(list.hashCode(), other.hashCode()); - } - - @Test - void testNestingHonorsOuterSeal() - { - List l2 = list.subList(0, list.size()); - List l3 = l2.subList(0, l2.size()); - List l4 = l3.subList(0, l3.size()); - List l5 = l4.subList(0, l4.size()); - l5.add(40); - assertEquals(list.size(), 4); - assertEquals(list.get(3), 40); - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> l5.add(50)); - sealedState = false; - l5.add(50); - assertEquals(list.size(), 5); - } -} diff --git a/src/test/java/com/cedarsoftware/util/SealableMapTest.java b/src/test/java/com/cedarsoftware/util/SealableMapTest.java deleted file mode 100644 index 55bfba837..000000000 --- a/src/test/java/com/cedarsoftware/util/SealableMapTest.java +++ /dev/null @@ -1,176 +0,0 @@ -package com.cedarsoftware.util; - -import java.util.AbstractMap; -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.function.Supplier; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static com.cedarsoftware.util.MapUtilities.mapOf; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @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. - */ -class SealableMapTest { - - private SealableMap map; - private volatile boolean sealedState = false; - private Supplier sealedSupplier = () -> sealedState; - - @BeforeEach - void setUp() { - map = new SealableMap<>(sealedSupplier); - map.put("one", 1); - map.put("two", 2); - map.put("three", 3); - map.put(null, null); - } - - @Test - void testPutWhenUnsealed() { - assertEquals(1, map.get("one")); - map.put("four", 4); - assertEquals(4, map.get("four")); - } - - @Test - void testRemoveWhenUnsealed() { - assertEquals(1, map.get("one")); - map.remove("one"); - assertNull(map.get("one")); - } - - @Test - void testPutWhenSealed() { - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> map.put("five", 5)); - } - - @Test - void testRemoveWhenSealed() { - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> map.remove("one")); - } - - @Test - void testModifyEntrySetWhenSealed() { - Set> entries = map.entrySet(); - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> entries.removeIf(e -> null == e.getKey())); - assertThrows(UnsupportedOperationException.class, () -> entries.iterator().remove()); - } - - @Test - void testModifyKeySetWhenSealed() { - Set keys = map.keySet(); - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> keys.remove("two")); - assertThrows(UnsupportedOperationException.class, keys::clear); - } - - @Test - void testModifyValuesWhenSealed() { - Collection values = map.values(); - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> values.remove(3)); - assertThrows(UnsupportedOperationException.class, values::clear); - } - - @Test - void testClearWhenSealed() { - sealedState = true; - assertThrows(UnsupportedOperationException.class, map::clear); - } - - @Test - void testPutAllWhenSealed() { - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> map.putAll(mapOf("ten", 10))); - } - - @Test - void testSealAndUnseal() { - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> map.put("six", 6)); - sealedState = false; - map.put("six", 6); - assertEquals(6, map.get("six")); - } - - @Test - void testEntrySetFunctionality() { - Set> entries = map.entrySet(); - assertNotNull(entries); - assertTrue(entries.stream().anyMatch(e -> "one".equals(e.getKey()) && e.getValue().equals(1))); - assertTrue(entries.stream().anyMatch(e -> e.getKey() == null && e.getValue() == null)); - - sealedState = true; - Map.Entry entry = new AbstractMap.SimpleImmutableEntry<>("five", 5); - assertThrows(UnsupportedOperationException.class, () -> entries.add(entry)); - } - - @Test - void testKeySetFunctionality() { - Set keys = map.keySet(); - assertNotNull(keys); - assertTrue(keys.contains("two")); - - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> keys.add("five")); - } - - @Test - void testValuesFunctionality() { - Collection values = map.values(); - assertNotNull(values); - assertTrue(values.contains(3)); - - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> values.add(5)); - } - - @Test - void testMapEquality() { - SealableMap anotherMap = new SealableMap<>(sealedSupplier); - anotherMap.put("one", 1); - anotherMap.put("two", 2); - anotherMap.put("three", 3); - anotherMap.put(null, null); - - assertEquals(map, anotherMap); - } - - @Test - void testNullKey() { - map.put(null, 99); - assert map.get(null) == 99; - } - - @Test - void testNullValue() { - map.put("99", null); - assert map.get("99") == null; - } -} diff --git a/src/test/java/com/cedarsoftware/util/SealableNavigableMapSubsetTest.java b/src/test/java/com/cedarsoftware/util/SealableNavigableMapSubsetTest.java deleted file mode 100644 index bb31477e4..000000000 --- a/src/test/java/com/cedarsoftware/util/SealableNavigableMapSubsetTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.cedarsoftware.util; - -import java.util.NavigableMap; -import java.util.TreeMap; -import java.util.function.Supplier; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * @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. - */ -class SealableNavigableMapSubsetTest { - private SealableNavigableMap unmodifiableMap; - private volatile boolean sealedState = false; - private final Supplier sealedSupplier = () -> sealedState; - - @BeforeEach - void setUp() { - NavigableMap testMap = new TreeMap<>(); - for (int i = 10; i <= 100; i += 10) { - testMap.put(i, String.valueOf(i)); - } - unmodifiableMap = new SealableNavigableMap<>(testMap, sealedSupplier); - } - - @Test - void testSubMap() { - NavigableMap subMap = unmodifiableMap.subMap(30, true, 70, true); - assertEquals(5, subMap.size(), "SubMap size should initially include keys 30, 40, 50, 60, 70"); - - assertThrows(IllegalArgumentException.class, () -> subMap.put(25, "25"), "Adding key 25 should fail as it is outside the bounds"); - assertNull(subMap.put(35, "35"), "Adding key 35 should succeed"); - assertEquals(6, subMap.size(), "SubMap size should now be 6"); - assertEquals(11, unmodifiableMap.size(), "Enclosing map should reflect the addition"); - - assertNull(subMap.remove(10), "Removing key 10 should fail as it is outside the bounds"); - assertEquals("40", subMap.remove(40), "Removing key 40 should succeed"); - assertEquals(5, subMap.size(), "SubMap size should be back to 5 after removal"); - assertEquals(10, unmodifiableMap.size(), "Enclosing map should reflect the removal"); - - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> subMap.put(60, "60"), "Modification should fail when sealed"); - } - - @Test - void testHeadMap() { - NavigableMap headMap = unmodifiableMap.headMap(50, true); - assertEquals(5, headMap.size(), "HeadMap should include keys up to and including 50"); - - assertThrows(IllegalArgumentException.class, () -> headMap.put(55, "55"), "Adding key 55 should fail as it is outside the bounds"); - assertNull(headMap.put(5, "5"), "Adding key 5 should succeed"); - assertEquals(6, headMap.size(), "HeadMap size should now be 6"); - assertEquals(11, unmodifiableMap.size(), "Enclosing map should reflect the addition"); - - assertNull(headMap.remove(60), "Removing key 60 should fail as it is outside the bounds"); - assertEquals("20", headMap.remove(20), "Removing key 20 should succeed"); - assertEquals(5, headMap.size(), "HeadMap size should be back to 5 after removal"); - assertEquals(10, unmodifiableMap.size(), "Enclosing map should reflect the removal"); - - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> headMap.put(10, "10"), "Modification should fail when sealed"); - } - - @Test - void testTailMap() { - NavigableMap tailMap = unmodifiableMap.tailMap(50, true); - assertEquals(6, tailMap.size(), "TailMap should include keys from 50 to 100"); - - assertThrows(IllegalArgumentException.class, () -> tailMap.put(45, "45"), "Adding key 45 should fail as it is outside the bounds"); - assertNull(tailMap.put(110, "110"), "Adding key 110 should succeed"); - assertEquals(7, tailMap.size(), "TailMap size should now be 7"); - assertEquals(11, unmodifiableMap.size(), "Enclosing map should reflect the addition"); - - assertNull(tailMap.remove(40), "Removing key 40 should fail as it is outside the bounds"); - assertEquals("60", tailMap.remove(60), "Removing key 60 should succeed"); - assertEquals(6, tailMap.size(), "TailMap size should be back to 6 after removal"); - assertEquals(10, unmodifiableMap.size(), "Enclosing map should reflect the removal"); - - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> tailMap.put(80, "80"), "Modification should fail when sealed"); - } -} \ No newline at end of file diff --git a/src/test/java/com/cedarsoftware/util/SealableNavigableMapTest.java b/src/test/java/com/cedarsoftware/util/SealableNavigableMapTest.java deleted file mode 100644 index 6eea6f761..000000000 --- a/src/test/java/com/cedarsoftware/util/SealableNavigableMapTest.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.cedarsoftware.util; - -import java.util.Iterator; -import java.util.Map; -import java.util.NavigableMap; -import java.util.function.Supplier; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @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. - */ -class SealableNavigableMapTest { - - private NavigableMap map; - private SealableNavigableMap unmodifiableMap; - private Supplier sealedSupplier; - private boolean sealed; - - @BeforeEach - void setUp() { - sealed = false; - sealedSupplier = () -> sealed; - - map = new ConcurrentNavigableMapNullSafe<>(); - map.put("three", 3); - map.put(null, null); - map.put("one", 1); - map.put("two", 2); - - unmodifiableMap = new SealableNavigableMap<>(map, sealedSupplier); - } - - @Test - void testMutationsWhenUnsealed() { - assertFalse(sealedSupplier.get(), "Map should start unsealed."); - assertEquals(4, unmodifiableMap.size()); - unmodifiableMap.put("four", 4); - assertEquals(Integer.valueOf(4), unmodifiableMap.get("four")); - assertTrue(unmodifiableMap.containsKey("four")); - } - - @Test - void testSealedMutationsThrowException() { - sealed = true; - assertThrows(UnsupportedOperationException.class, () -> unmodifiableMap.put("five", 5)); - assertThrows(UnsupportedOperationException.class, () -> unmodifiableMap.remove("one")); - assertThrows(UnsupportedOperationException.class, unmodifiableMap::clear); - } - - @Test - void testEntrySetValueWhenSealed() { - Map.Entry entry = unmodifiableMap.firstEntry(); - sealed = true; - assertThrows(UnsupportedOperationException.class, () -> entry.setValue(10)); - } - - @Test - void testKeySetViewReflectsChanges() { - unmodifiableMap.put("five", 5); - assertTrue(unmodifiableMap.keySet().contains("five")); - sealed = true; - assertThrows(UnsupportedOperationException.class, () -> unmodifiableMap.keySet().remove("five")); - } - - @Test - void testValuesViewReflectsChanges() { - unmodifiableMap.put("six", 6); - assertTrue(unmodifiableMap.values().contains(6)); - sealed = true; - assertThrows(UnsupportedOperationException.class, () -> unmodifiableMap.values().remove(6)); - } - - @Test - void testSubMapViewReflectsChanges2() { - // SubMap from "one" to "three", only includes "one" and "three" - NavigableMap subMap = unmodifiableMap.subMap("one", true, "three", true); - assertEquals(2, subMap.size()); // Should only include "one" and "three" - assertTrue(subMap.containsKey("one") && subMap.containsKey("three")); - assertFalse(subMap.containsKey("two")); // "two" should not be included - - // Adding a key that's lexicographically after "three" - unmodifiableMap.put("two-and-half", 2); - assertFalse(subMap.containsKey("two-and-half")); // Should not be visible in the submap - assertEquals(2, subMap.size()); // Size should remain as "two-and-half" is out of range - unmodifiableMap.put("pop", 93); - assertTrue(subMap.containsKey("pop")); - - subMap.put("poop", 37); - assertTrue(unmodifiableMap.containsKey("poop")); - - // Sealing and testing immutability - sealed = true; - assertThrows(UnsupportedOperationException.class, () -> subMap.put("zero", 0)); // Immutable (and outside range) - sealed = false; - assertThrows(java.lang.IllegalArgumentException.class, () -> subMap.put("zero", 0)); // outside range - } - - @Test - void testIteratorsThrowWhenSealed() { - Iterator keyIterator = unmodifiableMap.navigableKeySet().iterator(); - Iterator> entryIterator = unmodifiableMap.entrySet().iterator(); - - while (keyIterator.hasNext()) { - keyIterator.next(); - sealed = true; - assertThrows(UnsupportedOperationException.class, keyIterator::remove); - sealed = false; - } - - while (entryIterator.hasNext()) { - Map.Entry entry = entryIterator.next(); - sealed = true; - assertThrows(UnsupportedOperationException.class, () -> entry.setValue(999)); - sealed = false; - } - } - - @Test - void testDescendingMapReflectsChanges() { - unmodifiableMap.put("zero", 0); - NavigableMap descendingMap = unmodifiableMap.descendingMap(); - assertTrue(descendingMap.containsKey("zero")); - - sealed = true; - assertThrows(UnsupportedOperationException.class, () -> descendingMap.put("minus one", -1)); - } -} diff --git a/src/test/java/com/cedarsoftware/util/SealableNavigableSetTest.java b/src/test/java/com/cedarsoftware/util/SealableNavigableSetTest.java deleted file mode 100644 index 623744f7b..000000000 --- a/src/test/java/com/cedarsoftware/util/SealableNavigableSetTest.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.cedarsoftware.util; - -import java.util.Iterator; -import java.util.NavigableSet; -import java.util.Set; -import java.util.function.Supplier; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * @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. - */ -class SealableNavigableSetTest { - - private SealableNavigableSet set; - private volatile boolean sealedState = false; - private Supplier sealedSupplier = () -> sealedState; - - @BeforeEach - void setUp() { - set = new SealableNavigableSet<>(sealedSupplier); - set.add(null); - set.add(30); - set.add(10); - set.add(20); - } - - @Test - void testIteratorModificationException() { - Iterator iterator = set.iterator(); - sealedState = true; - assertDoesNotThrow(iterator::next); - assertThrows(UnsupportedOperationException.class, iterator::remove); - } - - @Test - void testDescendingIteratorModificationException() { - Iterator iterator = set.descendingIterator(); - sealedState = true; - assertDoesNotThrow(iterator::next); - assertThrows(UnsupportedOperationException.class, iterator::remove); - } - - @Test - void testTailSetModificationException() { - NavigableSet tailSet = set.tailSet(20, true); - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> tailSet.add(40)); - assertThrows(UnsupportedOperationException.class, tailSet::clear); - } - - @Test - void testHeadSetModificationException() { - NavigableSet headSet = set.headSet(20, false); - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> headSet.add(5)); - assertThrows(UnsupportedOperationException.class, headSet::clear); - } - - @Test - void testSubSetModificationException() { - NavigableSet subSet = set.subSet(10, true, 30, true); - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> subSet.add(25)); - assertThrows(UnsupportedOperationException.class, subSet::clear); - } - - @Test - void testDescendingSetModificationException() { - NavigableSet descendingSet = set.descendingSet(); - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> descendingSet.add(5)); - assertThrows(UnsupportedOperationException.class, descendingSet::clear); - } - - @Test - void testSealAfterModification() { - Iterator iterator = set.iterator(); - NavigableSet tailSet = set.tailSet(20, true); - sealedState = true; - assertThrows(UnsupportedOperationException.class, iterator::remove); - assertThrows(UnsupportedOperationException.class, () -> tailSet.add(40)); - } - - @Test - void testSubset() - { - Set subset = set.subSet(5, true, 25, true); - assertEquals(subset.size(), 2); - subset.add(5); - assertEquals(subset.size(), 3); - subset.add(25); - assertEquals(subset.size(), 4); - assertThrows(IllegalArgumentException.class, () -> subset.add(26)); - assertEquals(set.size(), 6); - } - - @Test - void testSubset2() - { - Set subset = set.subSet(5, 25); - assertEquals(subset.size(), 2); - assertThrows(IllegalArgumentException.class, () -> subset.add(4)); - subset.add(5); - assertEquals(subset.size(), 3); - assertThrows(IllegalArgumentException.class, () -> subset.add(25)); - assertEquals(5, set.size()); - } -} diff --git a/src/test/java/com/cedarsoftware/util/SealableNavigableSubsetTest.java b/src/test/java/com/cedarsoftware/util/SealableNavigableSubsetTest.java deleted file mode 100644 index 98965d476..000000000 --- a/src/test/java/com/cedarsoftware/util/SealableNavigableSubsetTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.cedarsoftware.util; - -import java.util.NavigableSet; -import java.util.TreeSet; -import java.util.function.Supplier; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @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. - */ -class SealableNavigableSubsetTest { - private SealableNavigableSet unmodifiableSet; - private volatile boolean sealedState = false; - private final Supplier sealedSupplier = () -> sealedState; - - @BeforeEach - void setUp() { - NavigableSet testSet = new TreeSet<>(); - for (int i = 10; i <= 100; i += 10) { - testSet.add(i); - } - unmodifiableSet = new SealableNavigableSet<>(testSet, sealedSupplier); - } - - @Test - void testSubSet() { - NavigableSet subSet = unmodifiableSet.subSet(30, true, 70, true); - assertEquals(5, subSet.size(), "SubSet size should initially include 30, 40, 50, 60, 70"); - - assertThrows(IllegalArgumentException.class, () -> subSet.add(25), "Adding 25 should fail as it is outside the bounds"); - assertTrue(subSet.add(35), "Adding 35 should succeed"); - assertEquals(6, subSet.size(), "SubSet size should now be 6"); - assertEquals(11, unmodifiableSet.size(), "Enclosing set should reflect the addition"); - - assertFalse(subSet.remove(10), "Removing 10 should fail as it is outside the bounds"); - assertTrue(subSet.remove(40), "Removing 40 should succeed"); - assertEquals(5, subSet.size(), "SubSet size should be back to 5 after removal"); - assertEquals(10, unmodifiableSet.size(), "Enclosing set should reflect the removal"); - - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> subSet.add(60), "Modification should fail when sealed"); - - } - - @Test - void testHeadSet() { - NavigableSet headSet = unmodifiableSet.headSet(50, true); - assertEquals(5, headSet.size(), "HeadSet should include 10, 20, 30, 40, 50"); - - assertThrows(IllegalArgumentException.class, () -> headSet.add(55), "Adding 55 should fail as it is outside the bounds"); - assertTrue(headSet.add(5), "Adding 5 should succeed"); - assertEquals(6, headSet.size(), "HeadSet size should now be 6"); - assertEquals(11, unmodifiableSet.size(), "Enclosing set should reflect the addition"); - - assertFalse(headSet.remove(60), "Removing 60 should fail as it is outside the bounds"); - assertTrue(headSet.remove(20), "Removing 20 should succeed"); - assertEquals(5, headSet.size(), "HeadSet size should be back to 5 after removal"); - assertEquals(10, unmodifiableSet.size(), "Enclosing set should reflect the removal"); - - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> headSet.add(10), "Modification should fail when sealed"); - } - - @Test - void testTailSet() { - NavigableSet tailSet = unmodifiableSet.tailSet(50, true); - assertEquals(6, tailSet.size(), "TailSet should include 50, 60, 70, 80, 90, 100"); - - assertThrows(IllegalArgumentException.class, () -> tailSet.add(45), "Adding 45 should fail as it is outside the bounds"); - assertTrue(tailSet.add(110), "Adding 110 should succeed"); - assertEquals(7, tailSet.size(), "TailSet size should now be 7"); - assertEquals(11, unmodifiableSet.size(), "Enclosing set should reflect the addition"); - - assertFalse(tailSet.remove(40), "Removing 40 should fail as it is outside the bounds"); - assertTrue(tailSet.remove(60), "Removing 60 should succeed"); - assertEquals(6, tailSet.size(), "TailSet size should be back to 6 after removal"); - assertEquals(10, unmodifiableSet.size(), "Enclosing set should reflect the removal"); - - sealedState = true; - assertThrows(UnsupportedOperationException.class, () -> tailSet.add(80), "Modification should fail when sealed"); - } -} diff --git a/src/test/java/com/cedarsoftware/util/SealableSetTest.java b/src/test/java/com/cedarsoftware/util/SealableSetTest.java deleted file mode 100644 index 7c802f1ab..000000000 --- a/src/test/java/com/cedarsoftware/util/SealableSetTest.java +++ /dev/null @@ -1,227 +0,0 @@ -package com.cedarsoftware.util; - -import java.util.Arrays; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.function.Supplier; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static com.cedarsoftware.util.CollectionUtilities.setOf; -import static com.cedarsoftware.util.DeepEquals.deepEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @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. - */ -class SealableSetTest { - - private SealableSet set; - private volatile boolean sealed = false; - private Supplier sealedSupplier = () -> sealed; - - @BeforeEach - void setUp() { - set = new SealableSet<>(sealedSupplier); - set.add(10); - set.add(20); - set.add(null); - } - - @Test - void testAdd() { - assertTrue(set.add(30)); - assertTrue(set.contains(30)); - } - - @Test - void testRemove() { - assertTrue(set.remove(20)); - assertFalse(set.contains(20)); - } - - @Test - void testAddWhenSealed() { - sealed = true; - assertThrows(UnsupportedOperationException.class, () -> set.add(40)); - } - - @Test - void testRemoveWhenSealed() { - sealed = true; - assertThrows(UnsupportedOperationException.class, () -> set.remove(10)); - } - - @Test - void testIteratorRemoveWhenSealed() { - Iterator iterator = set.iterator(); - sealed = true; - iterator.next(); // Move to first element - assertThrows(UnsupportedOperationException.class, iterator::remove); - } - - @Test - void testClearWhenSealed() { - sealed = true; - assertThrows(UnsupportedOperationException.class, set::clear); - } - - @Test - void testIterator() { - // Set items could be in any order - Iterator iterator = set.iterator(); - assertTrue(iterator.hasNext()); - Integer value = iterator.next(); - assert value == null || value == 10 || value == 20; - value = iterator.next(); - assert value == null || value == 10 || value == 20; - value = iterator.next(); - assertFalse(iterator.hasNext()); - assertThrows(NoSuchElementException.class, iterator::next); - } - - @Test - void testRootSealStateHonored() { - Iterator iterator = set.iterator(); - iterator.next(); - sealed = true; - assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); - sealed = false; - iterator.remove(); - assertEquals(set.size(), 2); - iterator.next(); - iterator.remove(); - assertEquals(set.size(), 1); - iterator.next(); - iterator.remove(); - assertEquals(set.size(), 0); - } - - @Test - void testContainsAll() { - assertTrue(set.containsAll(Arrays.asList(10, 20))); - assertFalse(set.containsAll(Arrays.asList(10, 30))); - } - - @Test - void testRetainAll() { - set.retainAll(Arrays.asList(10)); - assertTrue(set.contains(10)); - assertFalse(set.contains(20)); - } - - @Test - void testRetainAllWhenSealed() { - sealed = true; - assertThrows(UnsupportedOperationException.class, () -> set.retainAll(Arrays.asList(10))); - } - - @Test - void testAddAll() { - set.addAll(Arrays.asList(30, 40)); - assertTrue(set.containsAll(Arrays.asList(30, 40))); - } - - @Test - void testAddAllWhenSealed() { - sealed = true; - assertThrows(UnsupportedOperationException.class, () -> set.addAll(Arrays.asList(30, 40))); - } - - @Test - void testRemoveAll() { - set.removeAll(Arrays.asList(10, 20, null)); - assertTrue(set.isEmpty()); - } - - @Test - void testRemoveAllWhenSealed() { - sealed = true; - assertThrows(UnsupportedOperationException.class, () -> set.removeAll(Arrays.asList(10, 20))); - } - - @Test - void testSize() { - assertEquals(3, set.size()); - } - - @Test - void testIsEmpty() { - assertFalse(set.isEmpty()); - set.clear(); - assertTrue(set.isEmpty()); - } - - @Test - void testToArray() { - assert deepEquals(setOf(10, 20, null), set); - } - - @Test - void testNullValueSupport() { - int size = set.size(); - set.add(null); - assert size == set.size(); - } - - @Test - void testToArrayGenerics() { - Integer[] arr = set.toArray(new Integer[0]); - boolean found10 = false; - boolean found20 = false; - boolean foundNull = false; - for (int i = 0; i < arr.length; i++) { - if (arr[i] == null) { - foundNull = true; - continue; - } - if (arr[i] == 10) { - found10 = true; - } - if (arr[i] == 20) { - found20 = true; - } - } - assertTrue(foundNull); - assertTrue(found10); - assertTrue(found20); - assert arr.length == 3; - } - - @Test - void testEquals() { - SealableSet other = new SealableSet<>(sealedSupplier); - other.add(10); - other.add(20); - other.add(null); - assertEquals(set, other); - other.add(30); - assertNotEquals(set, other); - } - - @Test - void testHashCode() { - int expectedHashCode = set.hashCode(); - set.add(30); - assertNotEquals(expectedHashCode, set.hashCode()); - } -} diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java index c62a6c755..236a79463 100644 --- a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java +++ b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java @@ -46,11 +46,19 @@ import java.util.function.Supplier; import java.util.stream.Stream; +import com.cedarsoftware.io.JsonIo; +import com.cedarsoftware.io.JsonIoException; +import com.cedarsoftware.io.ReadOptions; +import com.cedarsoftware.io.ReadOptionsBuilder; +import com.cedarsoftware.io.WriteOptions; +import com.cedarsoftware.io.WriteOptionsBuilder; import com.cedarsoftware.util.ClassUtilities; import com.cedarsoftware.util.CompactMap; +import com.cedarsoftware.util.DeepEquals; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -88,6 +96,8 @@ import static com.cedarsoftware.util.convert.MapConversions.VARIANT; import static com.cedarsoftware.util.convert.MapConversions.YEAR; import static com.cedarsoftware.util.convert.MapConversions.ZONE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; @@ -3577,7 +3587,10 @@ private static void loadByteArrayTest() }); TEST_DB.put(pair(ByteBuffer.class, byte[].class), new Object[][]{ {ByteBuffer.wrap(new byte[]{}), new byte[] {}, true}, + {ByteBuffer.wrap(new byte[]{-1}), new byte[] {-1}, true}, {ByteBuffer.wrap(new byte[]{1, 2}), new byte[] {1, 2}, true}, + {ByteBuffer.wrap(new byte[]{1, 2, -3}), new byte[] {1, 2, -3}, true}, + {ByteBuffer.wrap(new byte[]{-128, 0, 127, 16}), new byte[] {-128, 0, 127, 16}, true}, }); TEST_DB.put(pair(char[].class, byte[].class), new Object[][] { {new char[] {}, new byte[] {}, true}, @@ -3813,6 +3826,69 @@ private static Stream generateTestEverythingParamsInReverse() { return Stream.of(list.toArray(new Arguments[]{})); } + @Disabled + @ParameterizedTest(name = "{0}[{2}] ==> {1}[{3}]") + @MethodSource("generateTestEverythingParams") + void testJsonIo(String shortNameSource, String shortNameTarget, Object source, Object target, Class sourceClass, Class targetClass, int index) { + if (shortNameSource.equals("Void")) { + return; + } + if (sourceClass.equals(Timestamp.class)) { + return; + } + if (targetClass.equals(Timestamp.class)) { + return; + } + + if (!Map.class.isAssignableFrom(sourceClass)) { + return; + } + if (!Calendar.class.equals(targetClass)) { + return; + } +// if (!Calendar.class.isAssignableFrom(sourceClass)) { +// return; +// } +// if (!targetClass.equals(ByteBuffer.class)) { +// return; +// } + + System.out.println("source=" + sourceClass.getName()); + System.out.println("target=" + targetClass.getName()); + + Converter conv = new Converter(new ConverterOptions() { + @Override + public ZoneId getZoneId() { + return TOKYO_Z; + } + }); + WriteOptions writeOptions = new WriteOptionsBuilder().build(); + ReadOptions readOptions = new ReadOptionsBuilder().setZoneId(TOKYO_Z).build(); + String json = JsonIo.toJson(source, writeOptions); + if (target instanceof Throwable) { + Throwable t = (Throwable) target; + try { + Object x = JsonIo.toObjects(json, readOptions, targetClass); + System.out.println("x = " + x); + fail("This test: " + shortNameSource + " ==> " + shortNameTarget + " should have thrown: " + target.getClass().getName()); + } catch (Throwable e) { + if (e instanceof JsonIoException) { + e = e.getCause(); + } else { + System.out.println("*********************************************************"); + } + assertThat(e.getMessage()).contains(t.getMessage()); + assertEquals(e.getClass(), t.getClass()); + } + } else { + Object restored = JsonIo.toObjects(json, readOptions, targetClass); + if (!DeepEquals.deepEquals(restored, target)) { + System.out.println("restored = " + restored); + System.out.println("target = " + target); + } + } + } + @ParameterizedTest(name = "{0}[{2}] ==> {1}[{3}]") @MethodSource("generateTestEverythingParams") void testConvert(String shortNameSource, String shortNameTarget, Object source, Object target, Class sourceClass, Class targetClass, int index) { @@ -3824,9 +3900,6 @@ void testConvert(String shortNameSource, String shortNameTarget, Object source, } } - if (source instanceof Map && targetClass.equals(Throwable.class)) { - System.out.println(); - } if (source == null) { assertEquals(Void.class, sourceClass, "On the source-side of test input, null can only appear in the Void.class data"); } else {