Skip to content

Commit

Permalink
More tests and documentation added
Browse files Browse the repository at this point in the history
  • Loading branch information
jdereg committed Jan 9, 2025
1 parent f8c31b0 commit 89c226e
Show file tree
Hide file tree
Showing 5 changed files with 445 additions and 19 deletions.
18 changes: 3 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ A collection of high-performance Java utilities designed to enhance standard Jav

Available on [Maven Central](https://central.sonatype.com/search?q=java-util&namespace=com.cedarsoftware).
This library has <b>no dependencies</b> on other libraries for runtime.
The`.jar`file is `336K` and works with `JDK 1.8` through `JDK 23`.
The`.jar`file is `414K` and works with `JDK 1.8` through `JDK 23`.
The `.jar` file classes are version 52 `(JDK 1.8)`
## Compatibility

Expand Down Expand Up @@ -46,12 +46,11 @@ implementation 'com.cedarsoftware:java-util:2.18.0'
---
# java-util


### Sets
- **[CompactSet](userguide.md#compactset)** - Memory-efficient Set that dynamically adapts its storage structure based on size
- **[CaseInsensitiveSet](userguide.md#caseinsensitiveset)** - Set implementation with case-insensitive String handling
- **[ConcurrentSet](/src/main/java/com/cedarsoftware/util/ConcurrentSet.java)** - Thread-safe Set supporting null elements
- **[ConcurrentNavigableSetNullSafe](/src/main/java/com/cedarsoftware/util/ConcurrentNavigableSetNullSafe.java)** - Thread-safe NavigableSet supporting null elements
- **[ConcurrentSet](userguide.md#concurrentset)** - Thread-safe Set supporting null elements
- **[ConcurrentNavigableSetNullSafe](userguide.md#concurrentnavigablesetnullsafe)** - Thread-safe NavigableSet supporting null elements

### Maps
- **[CompactMap](/src/main/java/com/cedarsoftware/util/CompactMap.java)** - Memory-efficient Map that dynamically adapts its storage structure based on size
Expand Down Expand Up @@ -88,16 +87,5 @@ implementation 'com.cedarsoftware:java-util:2.18.0'
See [changelog.md](/changelog.md) for revision history.

---
### Sponsors
[![Alt text](https://www.yourkit.com/images/yklogo.png "YourKit")](https://www.yourkit.com/.net/profiler/index.jsp)

YourKit supports open source projects with its full-featured Java Profiler.
YourKit, LLC is the creator of <a href="https://www.yourkit.com/java/profiler/index.jsp">YourKit Java Profiler</a>
and <a href="https://www.yourkit.com/.net/profiler/index.jsp">YourKit .NET Profiler</a>,
innovative and intelligent tools for profiling Java and .NET applications.

<a href="https://www.jetbrains.com/idea/"><img alt="Intellij IDEA from JetBrains" src="https://s-media-cache-ak0.pinimg.com/236x/bd/f4/90/bdf49052dd79aa1e1fc2270a02ba783c.jpg" data-canonical-src="https://s-media-cache-ak0.pinimg.com/236x/bd/f4/90/bdf49052dd79aa1e1fc2270a02ba783c.jpg" width="100" height="100" /></a>
**Intellij IDEA**<hr>


By: John DeRegnaucourt and Kenny Partlow
3 changes: 3 additions & 0 deletions src/main/java/com/cedarsoftware/util/ClassUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,9 @@ private static class CachedConstructor {
* }</pre>
*/
public static Object newInstance(Converter converter, Class<?> c, Collection<?> argumentValues) {
if (c == null) {
throw new IllegalArgumentException("Class cannot be null");
}
throwIfSecurityConcern(ProcessBuilder.class, c);
throwIfSecurityConcern(Process.class, c);
throwIfSecurityConcern(ClassLoader.class, c);
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/cedarsoftware/util/ConcurrentSet.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cedarsoftware.util;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
Expand Down Expand Up @@ -163,7 +164,7 @@ public <T1> T1[] toArray(T1[] a) {
Object[] internalArray = set.toArray();
int size = internalArray.length;
if (a.length < size) {
a = (T1[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
a = (T1[]) Array.newInstance(a.getClass().getComponentType(), size);
}
for (int i = 0; i < size; i++) {
if (internalArray[i] == NullSentinel.NULL_ITEM) {
Expand Down
232 changes: 230 additions & 2 deletions src/test/java/com/cedarsoftware/util/ClassUtilitiesTest.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,253 @@
package com.cedarsoftware.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Stream;

import com.cedarsoftware.util.convert.Converter;
import com.cedarsoftware.util.convert.DefaultConverterOptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

class ClassUtilitiesTest {
// Example classes and interfaces for testing
interface TestInterface {}
interface SubInterface extends TestInterface {}
static class TestClass {}
static class SubClass extends TestClass implements TestInterface {}
static class AnotherClass {}
private static class SubClass extends TestClass implements TestInterface {}
private static class AnotherClass {}
private Converter converter;

// Test classes
static class NoArgConstructor {
public NoArgConstructor() {}
}

static class SingleArgConstructor {
private final String value;
public SingleArgConstructor(String value) {
this.value = value;
}
public String getValue() { return value; }
}

static class MultiArgConstructor {
private final String str;
private final int num;
public MultiArgConstructor(String str, int num) {
this.str = str;
this.num = num;
}
public String getStr() { return str; }
public int getNum() { return num; }
}

static class OverloadedConstructors {
private final String value;
private final int number;

public OverloadedConstructors() {
this("default", 0);
}

public OverloadedConstructors(String value) {
this(value, 0);
}

public OverloadedConstructors(String value, int number) {
this.value = value;
this.number = number;
}

public String getValue() { return value; }
public int getNumber() { return number; }
}

static class PrivateConstructor {
private String value;
private PrivateConstructor(String value) {
this.value = value;
}
public String getValue() { return value; }
}

static class PrimitiveConstructor {
private final int intValue;
private final boolean boolValue;

public PrimitiveConstructor(int intValue, boolean boolValue) {
this.intValue = intValue;
this.boolValue = boolValue;
}

public int getIntValue() { return intValue; }
public boolean getBoolValue() { return boolValue; }
}

@BeforeEach
void setUp() {
converter = new Converter(new DefaultConverterOptions());
}

@Test
@DisplayName("Should create instance with no-arg constructor")
void shouldCreateInstanceWithNoArgConstructor() {
Object instance = ClassUtilities.newInstance(converter, NoArgConstructor.class, null);
assertNotNull(instance);
assertTrue(instance instanceof NoArgConstructor);
}

@Test
@DisplayName("Should create instance with single argument")
void shouldCreateInstanceWithSingleArgument() {
List<Object> args = Collections.singletonList("test");
Object instance = ClassUtilities.newInstance(converter, SingleArgConstructor.class, args);

assertNotNull(instance);
assertTrue(instance instanceof SingleArgConstructor);
assertEquals("test", ((SingleArgConstructor) instance).getValue());
}

@Test
@DisplayName("Should create instance with multiple arguments")
void shouldCreateInstanceWithMultipleArguments() {
List<Object> args = Arrays.asList("test", 42);
Object instance = ClassUtilities.newInstance(converter, MultiArgConstructor.class, args);

assertNotNull(instance);
assertTrue(instance instanceof MultiArgConstructor);
MultiArgConstructor mac = (MultiArgConstructor) instance;
assertEquals("test", mac.getStr());
assertEquals(42, mac.getNum());
}

@Test
@DisplayName("Should handle private constructors")
void shouldHandlePrivateConstructors() {
List<Object> args = Collections.singletonList("private");
Object instance = ClassUtilities.newInstance(converter, PrivateConstructor.class, args);

assertNotNull(instance);
assertTrue(instance instanceof PrivateConstructor);
assertEquals("private", ((PrivateConstructor) instance).getValue());
}

@Test
@DisplayName("Should handle primitive parameters with null arguments")
void shouldHandlePrimitiveParametersWithNullArguments() {
Object instance = ClassUtilities.newInstance(converter, PrimitiveConstructor.class, null);

assertNotNull(instance);
assertTrue(instance instanceof PrimitiveConstructor);
PrimitiveConstructor pc = (PrimitiveConstructor) instance;
assertEquals(0, pc.getIntValue()); // default int value
assertFalse(pc.getBoolValue()); // default boolean value
}

@Test
@DisplayName("Should choose best matching constructor with overloads")
void shouldChooseBestMatchingConstructor() {
List<Object> args = Arrays.asList("custom", 42);
Object instance = ClassUtilities.newInstance(converter, OverloadedConstructors.class, args);

assertNotNull(instance);
assertTrue(instance instanceof OverloadedConstructors);
OverloadedConstructors oc = (OverloadedConstructors) instance;
assertEquals("custom", oc.getValue());
assertEquals(42, oc.getNumber());
}

@Test
@DisplayName("Should throw IllegalArgumentException for security-sensitive classes")
void shouldThrowExceptionForSecuritySensitiveClasses() {
Class<?>[] sensitiveClasses = {
ProcessBuilder.class,
Process.class,
ClassLoader.class,
Constructor.class,
Method.class,
Field.class
};

for (Class<?> sensitiveClass : sensitiveClasses) {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> ClassUtilities.newInstance(converter, sensitiveClass, null)
);
assertTrue(exception.getMessage().contains("security reasons"));
}
}

@Test
@DisplayName("Should throw IllegalArgumentException for interfaces")
void shouldThrowExceptionForInterfaces() {
assertThrows(IllegalArgumentException.class,
() -> ClassUtilities.newInstance(converter, Runnable.class, null));
}

@Test
@DisplayName("Should throw IllegalArgumentException for null class")
void shouldThrowExceptionForNullClass() {
assertThrows(IllegalArgumentException.class,
() -> ClassUtilities.newInstance(converter, null, null));
}

@ParameterizedTest
@MethodSource("provideArgumentMatchingCases")
@DisplayName("Should match constructor arguments correctly")
void shouldMatchConstructorArgumentsCorrectly(Class<?> clazz, List<Object> args, Object[] expectedValues) {
Object instance = ClassUtilities.newInstance(converter, clazz, args);
assertNotNull(instance);
assertArrayEquals(expectedValues, getValues(instance));
}

private static Stream<Arguments> provideArgumentMatchingCases() {
return Stream.of(
Arguments.of(
MultiArgConstructor.class,
Arrays.asList("test", 42),
new Object[]{"test", 42}
),
Arguments.of(
MultiArgConstructor.class,
Arrays.asList(42, "test"), // wrong order, should still match
new Object[]{"test", 42}
),
Arguments.of(
MultiArgConstructor.class,
Collections.singletonList("test"), // partial args
new Object[]{"test", 0} // default int value
)
);
}

private Object[] getValues(Object instance) {
if (instance instanceof MultiArgConstructor) {
MultiArgConstructor mac = (MultiArgConstructor) instance;
return new Object[]{mac.getStr(), mac.getNum()};
}
throw new IllegalArgumentException("Unsupported test class");
}

@Test
void testComputeInheritanceDistanceWithNulls() {
Expand Down
Loading

0 comments on commit 89c226e

Please sign in to comment.