Skip to content

Commit 9a17905

Browse files
committed
Add ClassUtils#asSubclassIfPossible and EnumUtils#getAssociatedEnumType
1 parent cab4cd4 commit 9a17905

File tree

4 files changed

+193
-22
lines changed

4 files changed

+193
-22
lines changed

src/main/java/ch/jalu/typeresolver/EnumUtils.java

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,54 @@ public static <T> Optional<T> tryValueOf(@Nullable Class<T> clazz, @Nullable Str
3636
}
3737
}
3838

39+
/**
40+
* Returns an optional of the enum entry with the given name if the provided class is an enum and has an entry that
41+
* matches the given name in a case-insensitive manner. Otherwise, an empty optional is returned.
42+
* The first found match is returned if multiple enum entries match the name.
43+
*
44+
* @param clazz the class to check for enum entries, or null
45+
* @param name the name to match (case-insensitive), or null
46+
* @param <T> the class type
47+
* @return optional of the enum entry if the class is an enum and the name matched; empty otherwise
48+
*/
49+
public static <T> Optional<T> tryValueOfCaseInsensitive(@Nullable Class<T> clazz, @Nullable String name) {
50+
if (clazz == null || !clazz.isEnum() || name == null) {
51+
return Optional.empty();
52+
}
53+
54+
for (T enumConstant : clazz.getEnumConstants()) {
55+
if (name.equalsIgnoreCase(((Enum<?>) enumConstant).name())) {
56+
return Optional.of(enumConstant);
57+
}
58+
}
59+
return Optional.empty();
60+
}
61+
62+
/**
63+
* Returns an optional with the class cast as enum extension, empty otherwise. Convenient to check if a class is
64+
* an enum and to continue working with it as such. This class only returns an optional with the given class if it
65+
* represents an enum type; use {@link #getAssociatedEnumType} if synthetic classes of enum entries should be
66+
* converted to their enum type.
67+
* <p>
68+
* Examples:<pre>{@code
69+
* Class<?> class1 = TimeUnit.class;
70+
* Class<?> class2 = String.class;
71+
*
72+
* EnumUtils.typeAsEnumClass(class1) = Optional.of(TimeUnit.class)
73+
* EnumUtils.typeAsEnumClass(class2) = Optional.empty()
74+
* }</pre>
75+
*
76+
* @param clazz the class to inspect, or null
77+
* @return optional with the enum type if applicable, otherwise empty optional
78+
*/
79+
@SuppressWarnings({"unchecked", "rawtypes"})
80+
public static Optional<Class<? extends Enum<?>>> asEnumClassIfPossible(@Nullable Class<?> clazz) {
81+
if (clazz != null && clazz.isEnum()) {
82+
return Optional.of((Class) clazz);
83+
}
84+
return Optional.empty();
85+
}
86+
3987
/**
4088
* Indicates whether the given class is an enum or a synthetic class of an enum entry. Synthetic classes are
4189
* created when an enum entry extends the enum type anonymously.
@@ -54,7 +102,7 @@ public static <T> Optional<T> tryValueOf(@Nullable Class<T> clazz, @Nullable Str
54102
* @return true if the class is an enum or the class of an enum entry
55103
*/
56104
public static boolean isEnumOrEnumEntryType(@Nullable Class<?> clazz) {
57-
return asEnumType(clazz).isPresent();
105+
return getAssociatedEnumType(clazz).isPresent();
58106
}
59107

60108
/**
@@ -77,7 +125,7 @@ public static boolean isEnumOrEnumEntryType(@Nullable Class<?> clazz) {
77125
* @return optional with the enum type if applicable, otherwise empty optional
78126
*/
79127
@SuppressWarnings({"unchecked", "rawtypes"})
80-
public static Optional<Class<? extends Enum<?>>> asEnumType(@Nullable Class<?> clazz) {
128+
public static Optional<Class<? extends Enum<?>>> getAssociatedEnumType(@Nullable Class<?> clazz) {
81129
if (clazz == null || !Enum.class.isAssignableFrom(clazz)) {
82130
return Optional.empty();
83131
}

src/main/java/ch/jalu/typeresolver/classutil/ClassUtils.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,32 @@ public static Class<?> loadClassOrThrow(String name) {
6262
}
6363
}
6464

65+
/**
66+
* Returns an Optional with the same class cast as subtype of the given {@code parent} if possible,
67+
* otherwise returns an empty optional.
68+
* <p>
69+
* Note that the behavior for primitive classes may be unintuitive:<pre>{@code
70+
* Optional<? extends Integer> result1 = asSubClassIfPossible(int.class, int.class); // Optional.of(int.class)
71+
* Optional<? extends Integer> result2 = asSubClassIfPossible(int.class, Integer.class); // Optional.empty()
72+
* }</pre>
73+
* See {@link ch.jalu.typeresolver.primitives.PrimitiveType#toReferenceType PrimitiveType#toReferenceType} to
74+
* unwrap primitive types.
75+
*
76+
* @param classToInspect the class to process
77+
* @param parent the parent type to check if the class is an extension of
78+
* @param <T> the parent type
79+
* @return optional with the class as extension of the parent, or empty optional if not possible
80+
* @see Class#asSubclass
81+
*/
82+
@SuppressWarnings("unchecked")
83+
public static <T> Optional<Class<? extends T>> asSubclassIfPossible(Class<?> classToInspect,
84+
Class<T> parent) {
85+
if (parent.isAssignableFrom(classToInspect)) {
86+
return Optional.of((Class<? extends T>) classToInspect);
87+
}
88+
return Optional.empty();
89+
}
90+
6591
/**
6692
* Returns the {@link Class#getName() class name} of the object null-safely, returning "null" if the object is null.
6793
* Use {@link #getSemanticName(Object)} to generate a null-safe, more user-appropriate name of the object's type.
@@ -124,7 +150,7 @@ public static String getSemanticName(@Nullable Object object) {
124150
/**
125151
* Returns a human-friendly string representation of the given class, or of the most meaningful associated type.
126152
* Concretely, this means any synthetic classes for enum entries are replaced by the enum type itself, as returned
127-
* by {@link EnumUtils#asEnumType}.
153+
* by {@link EnumUtils#getAssociatedEnumType}.
128154
* <p>
129155
* Prefer using {@link #getSemanticName(Object)} whenever possible, as more semantic types can be inferred based
130156
* on an object.
@@ -133,7 +159,7 @@ public static String getSemanticName(@Nullable Object object) {
133159
* @return semantic type as class name, or "null" as string (never null itself)
134160
*/
135161
public static String getSemanticName(@Nullable Class<?> clazz) {
136-
return EnumUtils.asEnumType(clazz)
162+
return EnumUtils.getAssociatedEnumType(clazz)
137163
.map(ClassUtils::createNameOfSemanticType)
138164
.orElseGet(() -> createNameOfSemanticType(clazz));
139165
}

src/test/java/ch/jalu/typeresolver/EnumUtilsTest.java

Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,17 @@ void shouldHaveValidEnumExtensionExample() {
4343
}
4444

4545
/**
46-
* Test the Javadoc on {@link EnumUtils#asEnumType}.
46+
* Test the Javadoc on {@link EnumUtils#getAssociatedEnumType}.
4747
*/
4848
@Test
49-
void shouldHaveValidJavadocExampleOnAsEnumType() {
49+
void shouldHaveValidJavadocExampleOnGetAssociatedEnumType() {
5050
Class<?> class1 = NumericShaper.Range.class;
5151
Class<?> class2 = NumericShaper.Range.ETHIOPIC.getClass(); // NumericShaper$Range$1.class
5252

53-
Optional<Class<? extends Enum<?>>> r1 = EnumUtils.asEnumType(class1); // = Optional.of(NumericShaper.Range.class)
54-
Optional<Class<? extends Enum<?>>> r2 = EnumUtils.asEnumType(class2); // = Optional.of(NumericShaper.Range.class)
55-
Optional<Class<? extends Enum<?>>> r3 = EnumUtils.asEnumType(null);// = Optional.empty()
56-
Optional<Class<? extends Enum<?>>> r4 = EnumUtils.asEnumType(int.class);// = Optional.empty()
53+
Optional<Class<? extends Enum<?>>> r1 = EnumUtils.getAssociatedEnumType(class1); // = Optional.of(NumericShaper.Range.class)
54+
Optional<Class<? extends Enum<?>>> r2 = EnumUtils.getAssociatedEnumType(class2); // = Optional.of(NumericShaper.Range.class)
55+
Optional<Class<? extends Enum<?>>> r3 = EnumUtils.getAssociatedEnumType(null); // = Optional.empty()
56+
Optional<Class<? extends Enum<?>>> r4 = EnumUtils.getAssociatedEnumType(int.class);// = Optional.empty()
5757

5858
assertThat(r1, equalTo(Optional.of(NumericShaper.Range.class)));
5959
assertThat(r2, equalTo(Optional.of(NumericShaper.Range.class)));
@@ -80,6 +80,31 @@ void shouldTryToResolveNameToEntry() {
8080
assertThat(EnumUtils.tryValueOf(TimeUnit.class, "WRONG"), equalTo(Optional.empty()));
8181
}
8282

83+
@Test
84+
void shouldTryToResolveNameToEntryCaseInsensitively() {
85+
// given / when / then
86+
assertThat(EnumUtils.tryValueOfCaseInsensitive(TestEnum.class, "FIRST"), equalTo(Optional.of(TestEnum.FIRST)));
87+
assertThat(EnumUtils.tryValueOfCaseInsensitive(TestEnum.class, "Second"), equalTo(Optional.of(TestEnum.SECOND)));
88+
assertThat(EnumUtils.tryValueOfCaseInsensitive(TestEnum.class, "third"), equalTo(Optional.of(TestEnum.THIRD)));
89+
assertThat(EnumUtils.tryValueOfCaseInsensitive(TimeUnit.class, "MINUTES"), equalTo(Optional.of(TimeUnit.MINUTES)));
90+
assertThat(EnumUtils.tryValueOfCaseInsensitive(TestEnum.NestedEnum.class, "SECOND"), equalTo(Optional.of(TestEnum.NestedEnum.SECOND)));
91+
92+
assertThat(EnumUtils.tryValueOfCaseInsensitive(EnumWithNonStandardEntryNames.class, "FIRST"), equalTo(Optional.of(EnumWithNonStandardEntryNames.first)));
93+
assertThat(EnumUtils.tryValueOfCaseInsensitive(EnumWithNonStandardEntryNames.class, "second"), equalTo(Optional.of(EnumWithNonStandardEntryNames.Second)));
94+
assertThat(EnumUtils.tryValueOfCaseInsensitive(EnumWithNonStandardEntryNames.class, "third"), equalTo(Optional.of(EnumWithNonStandardEntryNames.Third)));
95+
assertThat(EnumUtils.tryValueOfCaseInsensitive(EnumWithNonStandardEntryNames.class, "tHird"), equalTo(Optional.of(EnumWithNonStandardEntryNames.Third)));
96+
assertThat(EnumUtils.tryValueOfCaseInsensitive(EnumWithNonStandardEntryNames.class, "other"), equalTo(Optional.empty()));
97+
98+
assertThat(EnumUtils.tryValueOfCaseInsensitive(null, null), equalTo(Optional.empty()));
99+
assertThat(EnumUtils.tryValueOfCaseInsensitive(TestEnum.class, null), equalTo(Optional.empty()));
100+
assertThat(EnumUtils.tryValueOfCaseInsensitive(null, "ENTRY"), equalTo(Optional.empty()));
101+
102+
assertThat(EnumUtils.tryValueOfCaseInsensitive(TestEnum.class, "WRONG"), equalTo(Optional.empty()));
103+
assertThat(EnumUtils.tryValueOfCaseInsensitive(TestEnum.NestedEnum.class, "WRONG"), equalTo(Optional.empty()));
104+
assertThat(EnumUtils.tryValueOfCaseInsensitive(HashMap.class, "wrong"), equalTo(Optional.empty()));
105+
assertThat(EnumUtils.tryValueOfCaseInsensitive(TimeUnit.class, "WRONG"), equalTo(Optional.empty()));
106+
}
107+
83108
@Test
84109
void shouldDefineWhetherIsEnumRelatedClass() {
85110
// given / when / then
@@ -97,7 +122,7 @@ void shouldDefineWhetherIsEnumRelatedClass() {
97122
}
98123

99124
@Test
100-
void shouldReturnEnumClassIfApplicable() {
125+
void shouldCastClassAsEnumExtensionIfApplicable() {
101126
// given
102127
Class<?> clazz1 = TestEnum.class;
103128
Class<?> clazz2 = TestEnum.SECOND.getClass();
@@ -107,12 +132,44 @@ void shouldReturnEnumClassIfApplicable() {
107132
Class<?> clazz6 = TestEnum.NestedEnum.SECOND.getClass();
108133

109134
// when
110-
Optional<Class<? extends Enum<?>>> result1 = EnumUtils.asEnumType(clazz1);
111-
Optional<Class<? extends Enum<?>>> result2 = EnumUtils.asEnumType(clazz2);
112-
Optional<Class<? extends Enum<?>>> result3 = EnumUtils.asEnumType(clazz3);
113-
Optional<Class<? extends Enum<?>>> result4 = EnumUtils.asEnumType(clazz4);
114-
Optional<Class<? extends Enum<?>>> result5 = EnumUtils.asEnumType(clazz5);
115-
Optional<Class<? extends Enum<?>>> result6 = EnumUtils.asEnumType(clazz6);
135+
Optional<Class<? extends Enum<?>>> result1 = EnumUtils.asEnumClassIfPossible(clazz1);
136+
Optional<Class<? extends Enum<?>>> result2 = EnumUtils.asEnumClassIfPossible(clazz2);
137+
Optional<Class<? extends Enum<?>>> result3 = EnumUtils.asEnumClassIfPossible(clazz3);
138+
Optional<Class<? extends Enum<?>>> result4 = EnumUtils.asEnumClassIfPossible(clazz4);
139+
Optional<Class<? extends Enum<?>>> result5 = EnumUtils.asEnumClassIfPossible(clazz5);
140+
Optional<Class<? extends Enum<?>>> result6 = EnumUtils.asEnumClassIfPossible(clazz6);
141+
142+
// then
143+
assertThat(result1, equalTo(Optional.of(TestEnum.class)));
144+
assertThat(result2, equalTo(Optional.empty()));
145+
assertThat(result3, equalTo(Optional.empty()));
146+
assertThat(result4, equalTo(Optional.empty()));
147+
assertThat(result5, equalTo(Optional.of(TestEnum.NestedEnum.class)));
148+
assertThat(result6, equalTo(Optional.empty()));
149+
150+
assertThat(EnumUtils.getAssociatedEnumType(null), equalTo(Optional.empty()));
151+
assertThat(EnumUtils.getAssociatedEnumType(String.class), equalTo(Optional.empty()));
152+
assertThat(EnumUtils.getAssociatedEnumType(double[][].class), equalTo(Optional.empty()));
153+
assertThat(EnumUtils.getAssociatedEnumType(List.class), equalTo(Optional.empty()));
154+
}
155+
156+
@Test
157+
void shouldReturnAssociatedEnumClassIfApplicable() {
158+
// given
159+
Class<?> clazz1 = TestEnum.class;
160+
Class<?> clazz2 = TestEnum.SECOND.getClass();
161+
Class<?> clazz3 = TestEnum.Inner.class;
162+
Class<?> clazz4 = TestEnum.SECOND.getClass().getDeclaredClasses()[0];
163+
Class<?> clazz5 = TestEnum.NestedEnum.class;
164+
Class<?> clazz6 = TestEnum.NestedEnum.SECOND.getClass();
165+
166+
// when
167+
Optional<Class<? extends Enum<?>>> result1 = EnumUtils.getAssociatedEnumType(clazz1);
168+
Optional<Class<? extends Enum<?>>> result2 = EnumUtils.getAssociatedEnumType(clazz2);
169+
Optional<Class<? extends Enum<?>>> result3 = EnumUtils.getAssociatedEnumType(clazz3);
170+
Optional<Class<? extends Enum<?>>> result4 = EnumUtils.getAssociatedEnumType(clazz4);
171+
Optional<Class<? extends Enum<?>>> result5 = EnumUtils.getAssociatedEnumType(clazz5);
172+
Optional<Class<? extends Enum<?>>> result6 = EnumUtils.getAssociatedEnumType(clazz6);
116173

117174
// then
118175
assertThat(result1, equalTo(Optional.of(TestEnum.class)));
@@ -122,10 +179,10 @@ void shouldReturnEnumClassIfApplicable() {
122179
assertThat(result5, equalTo(Optional.of(TestEnum.NestedEnum.class)));
123180
assertThat(result6, equalTo(Optional.of(TestEnum.NestedEnum.class)));
124181

125-
assertThat(EnumUtils.asEnumType(null), equalTo(Optional.empty()));
126-
assertThat(EnumUtils.asEnumType(String.class), equalTo(Optional.empty()));
127-
assertThat(EnumUtils.asEnumType(double[][].class), equalTo(Optional.empty()));
128-
assertThat(EnumUtils.asEnumType(List.class), equalTo(Optional.empty()));
182+
assertThat(EnumUtils.getAssociatedEnumType(null), equalTo(Optional.empty()));
183+
assertThat(EnumUtils.getAssociatedEnumType(String.class), equalTo(Optional.empty()));
184+
assertThat(EnumUtils.getAssociatedEnumType(double[][].class), equalTo(Optional.empty()));
185+
assertThat(EnumUtils.getAssociatedEnumType(List.class), equalTo(Optional.empty()));
129186
}
130187

131188
@Test
@@ -162,7 +219,8 @@ private enum NestedEnum {
162219

163220
FIRST,
164221

165-
SECOND()
222+
SECOND() {
223+
}
166224

167225
}
168226

@@ -173,4 +231,12 @@ static class Inner2d {
173231
}
174232
}
175233
}
234+
235+
private enum EnumWithNonStandardEntryNames {
236+
237+
first,
238+
Second,
239+
Third, // 1
240+
tHird // 2
241+
}
176242
}

src/test/java/ch/jalu/typeresolver/classutil/ClassUtilsTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.junit.jupiter.params.provider.EnumSource;
99

1010
import java.awt.font.NumericShaper;
11+
import java.io.Serializable;
1112
import java.lang.annotation.Annotation;
1213
import java.lang.reflect.Array;
1314
import java.time.DayOfWeek;
@@ -97,6 +98,36 @@ void shouldLoadClassOrThrowException() {
9798
}
9899
}
99100

101+
@Test
102+
void shouldCastClassAsSubclass() {
103+
// given / when
104+
Optional<Class<? extends CharSequence>> result1 = ClassUtils.asSubclassIfPossible(String.class, CharSequence.class);
105+
Optional<Class<? extends Number>> result2 = ClassUtils.asSubclassIfPossible(Integer.class, Number.class);
106+
Optional<Class<? extends Serializable>> result3 = ClassUtils.asSubclassIfPossible(Integer.class, Serializable.class);
107+
Optional<Class<? extends Number[]>> result4 = ClassUtils.asSubclassIfPossible(Integer[].class, Number[].class);
108+
Optional<Class<? extends Double>> result5 = ClassUtils.asSubclassIfPossible(Double.class, Double.class);
109+
Optional<Class<? extends Integer>> result6 = ClassUtils.asSubclassIfPossible(int.class, int.class);
110+
111+
// then
112+
assertThat(result1, equalTo(Optional.of(String.class)));
113+
assertThat(result2, equalTo(Optional.of(Integer.class)));
114+
assertThat(result3, equalTo(Optional.of(Integer.class)));
115+
assertThat(result4, equalTo(Optional.of(Integer[].class)));
116+
assertThat(result5, equalTo(Optional.of(Double.class)));
117+
assertThat(result6, equalTo(Optional.of(int.class)));
118+
}
119+
120+
@Test
121+
void shouldNotCastAsSubclassWhenNotPossible() {
122+
// given / when / then
123+
assertThat(ClassUtils.asSubclassIfPossible(String.class, Number.class), equalTo(Optional.empty()));
124+
assertThat(ClassUtils.asSubclassIfPossible(Number.class, Integer.class), equalTo(Optional.empty()));
125+
assertThat(ClassUtils.asSubclassIfPossible(Integer[].class, CharSequence[].class), equalTo(Optional.empty()));
126+
127+
assertThat(ClassUtils.asSubclassIfPossible(int.class, Integer.class), equalTo(Optional.empty()));
128+
assertThat(ClassUtils.asSubclassIfPossible(Integer.class, int.class), equalTo(Optional.empty()));
129+
}
130+
100131
@Test
101132
void shouldReturnClassNameNullSafely() {
102133
// given / when / then

0 commit comments

Comments
 (0)