Skip to content

Commit 7b62eec

Browse files
committed
#109 Create ConstructorUtils
1 parent 7610007 commit 7b62eec

File tree

4 files changed

+261
-2
lines changed

4 files changed

+261
-2
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package ch.jalu.typeresolver.reflect;
2+
3+
import java.lang.reflect.Constructor;
4+
import java.util.Arrays;
5+
import java.util.Optional;
6+
import java.util.stream.Collectors;
7+
8+
/**
9+
* Utils class for constructors.
10+
*/
11+
public final class ConstructorUtils {
12+
13+
private ConstructorUtils() {
14+
}
15+
16+
/**
17+
* Returns the specified constructor if it exists, otherwise returns an empty Optional.
18+
*
19+
* @param clazz the class to search in
20+
* @param parameterTypes the parameter types the constructor must match
21+
* @param <T> class type
22+
* @return optional with the matching constructor, or empty optional
23+
*/
24+
public static <T> Optional<Constructor<T>> tryFindConstructor(Class<T> clazz, Class<?>... parameterTypes) {
25+
try {
26+
return Optional.of(clazz.getDeclaredConstructor(parameterTypes));
27+
} catch (NoSuchMethodException ignore) {
28+
return Optional.empty();
29+
}
30+
}
31+
32+
/**
33+
* Returns the specified constructor, throwing an exception if it does not exist.
34+
*
35+
* @param clazz the class to search in
36+
* @param parameterTypes the parameter types the constructor must match
37+
* @param <T> class type
38+
* @return the matching constructor
39+
*/
40+
public static <T> Constructor<T> getConstructorOrThrow(Class<T> clazz, Class<?>... parameterTypes) {
41+
try {
42+
return clazz.getDeclaredConstructor(parameterTypes);
43+
} catch (NoSuchMethodException e) {
44+
throw new IllegalStateException("No constructor on '" + clazz
45+
+ "' matches the parameter types: [" + createListOfParamTypes(parameterTypes) + "]", e);
46+
}
47+
}
48+
49+
/**
50+
* Calls the given constructor with the provided values. Throws a runtime exception if any exception occurs.
51+
*
52+
* @param constructor the constructor to invoke
53+
* @param values the values to invoke the constructor with
54+
* @param <T> class type of the constructor
55+
* @return the constructed object
56+
*/
57+
public static <T> T invokeConstructor(Constructor<T> constructor, Object... values) {
58+
try {
59+
return constructor.newInstance(values);
60+
} catch (IllegalArgumentException e) {
61+
throw new IllegalArgumentException("Failed to call constructor for '"
62+
+ constructor.getDeclaringClass() + "'", e);
63+
} catch (ReflectiveOperationException e) {
64+
throw new IllegalStateException("Failed to call constructor for '"
65+
+ constructor.getDeclaringClass() + "'", e);
66+
}
67+
}
68+
69+
/**
70+
* Creates a new object from the specified class's zero-args constructor. The constructor may be private.
71+
* An exception is thrown if the class does not have a zero-args constructor.
72+
*
73+
* @param clazz the class to instantiate
74+
* @param <T> the class type
75+
* @return new instance of the class
76+
*/
77+
public static <T> T newInstanceFromZeroArgsConstructor(Class<T> clazz) {
78+
try {
79+
Constructor<T> constructor = clazz.getDeclaredConstructor();
80+
if (!constructor.isAccessible()) {
81+
constructor.setAccessible(true);
82+
}
83+
return invokeConstructor(constructor);
84+
} catch (NoSuchMethodException e) {
85+
throw new IllegalStateException(
86+
"Expected class '" + clazz.getName() + "' to have a zero-args constructor", e);
87+
}
88+
}
89+
90+
/**
91+
* Creates a textual representation of the given constructor, using the involved type's simple name,
92+
* {@link Class#getSimpleName()}.
93+
* <p>
94+
* Examples: "Integer(int)", "HashMap(int, float)"
95+
*
96+
* @param constructor the constructor
97+
* @return string representation of the constructor
98+
*/
99+
public static String simpleToString(Constructor<?> constructor) {
100+
String className = constructor.getDeclaringClass().getSimpleName();
101+
return className + "(" + createListOfParamTypes(constructor.getParameterTypes()) + ")";
102+
}
103+
104+
private static String createListOfParamTypes(Class<?>[] paramTypes) {
105+
return Arrays.stream(paramTypes)
106+
.map(Class::getSimpleName)
107+
.collect(Collectors.joining(", "));
108+
}
109+
}

src/main/java/ch/jalu/typeresolver/FieldUtils.java renamed to src/main/java/ch/jalu/typeresolver/reflect/FieldUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package ch.jalu.typeresolver;
1+
package ch.jalu.typeresolver.reflect;
22

33
import ch.jalu.typeresolver.classutil.ClassUtils;
44

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package ch.jalu.typeresolver.reflect;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.lang.reflect.Constructor;
6+
import java.util.ArrayList;
7+
import java.util.Optional;
8+
9+
import static org.hamcrest.MatcherAssert.assertThat;
10+
import static org.hamcrest.Matchers.equalTo;
11+
import static org.hamcrest.Matchers.instanceOf;
12+
import static org.hamcrest.Matchers.notNullValue;
13+
import static org.junit.jupiter.api.Assertions.assertThrows;
14+
15+
/**
16+
* Test for {@link ConstructorUtils}.
17+
*/
18+
class ConstructorUtilsTest {
19+
20+
@Test
21+
void shouldReturnConstructorIfApplicable() throws NoSuchMethodException {
22+
// given / when
23+
Optional<Constructor<Sample>> constr1 = ConstructorUtils.tryFindConstructor(Sample.class, int.class, String.class);
24+
Optional<Constructor<Sample>> constr2 = ConstructorUtils.tryFindConstructor(Sample.class, int.class, long.class);
25+
26+
// then
27+
assertThat(constr1, equalTo(Optional.of(Sample.class.getDeclaredConstructor(int.class, String.class))));
28+
assertThat(constr2, equalTo(Optional.empty()));
29+
}
30+
31+
@Test
32+
void shouldReturnConstructor() throws NoSuchMethodException {
33+
// given / when
34+
Constructor<Sample> constr = ConstructorUtils.getConstructorOrThrow(Sample.class, int.class, String.class);
35+
36+
// then
37+
assertThat(constr, equalTo(Sample.class.getDeclaredConstructor(int.class, String.class)));
38+
}
39+
40+
@Test
41+
void shouldThrowForNonExistentConstructor() {
42+
// given / when
43+
IllegalStateException ex = assertThrows(IllegalStateException.class,
44+
() -> ConstructorUtils.getConstructorOrThrow(Sample.class, String.class, int.class));
45+
46+
// then
47+
assertThat(ex.getMessage(), equalTo("No constructor on 'class ch.jalu.typeresolver.reflect.ConstructorUtilsTest$Sample' matches the parameter types: [String, int]"));
48+
}
49+
50+
@Test
51+
void shouldInvokeConstructor() throws NoSuchMethodException {
52+
// given
53+
Constructor<Sample> constr = Sample.class.getDeclaredConstructor(int.class, String.class);
54+
55+
// when
56+
Sample result = ConstructorUtils.invokeConstructor(constr, 3, "test");
57+
58+
// then
59+
assertThat(result.size, equalTo(3));
60+
assertThat(result.str, equalTo("test"));
61+
}
62+
63+
@Test
64+
void shouldWrapIllegalArgumentException() throws NoSuchMethodException {
65+
// given
66+
Constructor<Sample> constr = Sample.class.getDeclaredConstructor(int.class, String.class);
67+
68+
// when
69+
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
70+
() -> ConstructorUtils.invokeConstructor(constr, true, "test"));
71+
72+
// then
73+
assertThat(ex.getMessage(), equalTo("Failed to call constructor for 'class ch.jalu.typeresolver.reflect.ConstructorUtilsTest$Sample'"));
74+
assertThat(ex.getCause(), instanceOf(IllegalArgumentException.class));
75+
}
76+
77+
@Test
78+
void shouldWrapReflectiveOperationException() throws NoSuchMethodException {
79+
// given
80+
Constructor<Sample> constr = Sample.class.getDeclaredConstructor(int[].class);
81+
82+
// when
83+
IllegalStateException ex = assertThrows(IllegalStateException.class,
84+
() -> ConstructorUtils.invokeConstructor(constr, new int[0]));
85+
86+
// then
87+
assertThat(ex.getMessage(), equalTo("Failed to call constructor for 'class ch.jalu.typeresolver.reflect.ConstructorUtilsTest$Sample'"));
88+
assertThat(ex.getCause(), instanceOf(IllegalAccessException.class));
89+
}
90+
91+
@Test
92+
void shouldCreateObjectFromZeroArgsConstructor() {
93+
// given / when
94+
NoArgsBean noArgsBean = ConstructorUtils.newInstanceFromZeroArgsConstructor(NoArgsBean.class);
95+
ArrayList arrayList = ConstructorUtils.newInstanceFromZeroArgsConstructor(ArrayList.class);
96+
97+
// then
98+
assertThat(noArgsBean, notNullValue());
99+
assertThat(arrayList, notNullValue());
100+
}
101+
102+
@Test
103+
void shouldCreateObjectFromPrivateConstructor() {
104+
// given / when / then
105+
assertThat(ConstructorUtils.newInstanceFromZeroArgsConstructor(ConstructorUtils.class), notNullValue());
106+
}
107+
108+
@Test
109+
void shouldThrowForMissingZeroArgsConstructor() {
110+
// given / when
111+
IllegalStateException ex = assertThrows(IllegalStateException.class,
112+
() -> ConstructorUtils.newInstanceFromZeroArgsConstructor(Integer.class));
113+
114+
// then
115+
assertThat(ex.getMessage(), equalTo("Expected class 'java.lang.Integer' to have a zero-args constructor"));
116+
}
117+
118+
@Test
119+
void shouldCreateToStringForConstructor() throws NoSuchMethodException {
120+
// given
121+
Constructor<?> constr1 = NoArgsBean.class.getDeclaredConstructor();
122+
Constructor<?> constr2 = Integer.class.getDeclaredConstructor(int.class);
123+
Constructor<?> constr3 = Sample.class.getDeclaredConstructor(int.class, String.class);
124+
125+
// when / then
126+
assertThat(ConstructorUtils.simpleToString(constr1), equalTo("NoArgsBean()"));
127+
assertThat(ConstructorUtils.simpleToString(constr2), equalTo("Integer(int)"));
128+
assertThat(ConstructorUtils.simpleToString(constr3), equalTo("Sample(int, String)"));
129+
}
130+
131+
static final class Sample {
132+
133+
int size;
134+
String str;
135+
136+
Sample(int size, String str) {
137+
this.size = size;
138+
this.str = str;
139+
}
140+
141+
private Sample(int[] array) {
142+
}
143+
}
144+
145+
static final class NoArgsBean {
146+
147+
public NoArgsBean() {
148+
}
149+
}
150+
}

src/test/java/ch/jalu/typeresolver/FieldUtilsTest.java renamed to src/test/java/ch/jalu/typeresolver/reflect/FieldUtilsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package ch.jalu.typeresolver;
1+
package ch.jalu.typeresolver.reflect;
22

33
import org.junit.jupiter.api.Test;
44

0 commit comments

Comments
 (0)