Skip to content

Commit a812fed

Browse files
committed
Add interface checks to ClassUtils
Introduce `ClassUtils.isInterface()` and `ClassUtils.isFunctionalInterface()` to make interface detection and functional-interface introspection available through the utility API. The change also updates internal interface modifier usage, fixes a Javadoc reference, and adds coverage for standard and custom interface cases.
1 parent 102461a commit a812fed

2 files changed

Lines changed: 128 additions & 3 deletions

File tree

microsphere-java-core/src/main/java/io/microsphere/util/ClassUtils.java

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.microsphere.reflect.ConstructorUtils;
1616

1717
import java.io.File;
18+
import java.lang.reflect.Method;
1819
import java.lang.reflect.Modifier;
1920
import java.math.BigDecimal;
2021
import java.math.BigInteger;
@@ -56,7 +57,11 @@
5657
import static io.microsphere.lang.function.Predicates.EMPTY_PREDICATE_ARRAY;
5758
import static io.microsphere.logging.LoggerFactory.getLogger;
5859
import static io.microsphere.net.URLUtils.resolveProtocol;
60+
import static io.microsphere.reflect.MethodUtils.findMethods;
61+
import static io.microsphere.reflect.MethodUtils.isOverridenObjectMethod;
5962
import static io.microsphere.reflect.Modifier.isAnnotation;
63+
import static io.microsphere.reflect.Modifier.isBridge;
64+
import static io.microsphere.reflect.Modifier.isStatic;
6065
import static io.microsphere.reflect.Modifier.isSynthetic;
6166
import static io.microsphere.util.ArrayUtils.EMPTY_CLASS_ARRAY;
6267
import static io.microsphere.util.ArrayUtils.isEmpty;
@@ -73,7 +78,6 @@
7378
import static java.lang.Boolean.FALSE;
7479
import static java.lang.Boolean.TRUE;
7580
import static java.lang.reflect.Modifier.isAbstract;
76-
import static java.lang.reflect.Modifier.isInterface;
7781
import static java.util.Collections.addAll;
7882
import static java.util.Collections.emptyList;
7983
import static java.util.Collections.emptySet;
@@ -415,6 +419,24 @@ public static boolean isAbstractClass(@Nullable Class<?> type) {
415419
return isGeneralClass(type, TRUE);
416420
}
417421

422+
/**
423+
* Checks if the specified type is an interface.
424+
*
425+
* <h3>Example Usage</h3>
426+
* <pre>{@code
427+
* boolean result1 = ClassUtils.isInterface(List.class); // returns true
428+
* boolean result2 = ClassUtils.isInterface(String.class); // returns false
429+
* boolean result3 = ClassUtils.isInterface(null); // returns false
430+
* }</pre>
431+
*
432+
* @param type the type to check, may be {@code null}
433+
* @return {@code true} if the specified type is an interface, {@code false} otherwise
434+
* @see Class#isInterface()
435+
*/
436+
public static boolean isInterface(@Nullable Class<?> type) {
437+
return type != null && type.isInterface();
438+
}
439+
418440
/**
419441
* Checks if the specified type is a general class.
420442
* <p>
@@ -467,7 +489,7 @@ protected static boolean isGeneralClass(@Nullable Class<?> type, @Nullable Boole
467489
int mod = type.getModifiers();
468490
if (isAnnotation(mod)
469491
|| io.microsphere.reflect.Modifier.isEnum(mod)
470-
|| isInterface(mod)
492+
|| io.microsphere.reflect.Modifier.isInterface(mod)
471493
|| isSynthetic(mod)
472494
|| isPrimitive(type)
473495
|| isArray(type)) {
@@ -617,6 +639,55 @@ public static boolean isLambdaClass(@Nullable Class<?> type) {
617639
return isSyntheticClass(type) && getTypeName(type).contains("$$Lambda/");
618640
}
619641

642+
643+
/**
644+
* Checks if the specified class is a functional interface.
645+
* <p>
646+
* A functional interface is an interface that has exactly one abstract method.
647+
* This method returns {@code true} if the given class is an interface and has exactly one abstract method,
648+
* or if it is annotated with {@link FunctionalInterface}. It returns {@code false} otherwise, including for
649+
* non-interface classes or interfaces with more than one abstract method. It also returns {@code false} if
650+
* the class is {@code null}.
651+
* </p>
652+
*
653+
* <h3>Example Usage</h3>
654+
* <pre>{@code
655+
* @FunctionalInterface
656+
* interface MyFunctionalInterface {
657+
* void doSomething();
658+
* }
659+
*
660+
* boolean result1 = ClassUtils.isFunctionalInterface(MyFunctionalInterface.class); // returns true
661+
* boolean result2 = ClassUtils.isFunctionalInterface(Runnable.class); // returns true
662+
* boolean result3 = ClassUtils.isFunctionalInterface(List.class); // returns false (not a functional interface)
663+
* boolean result4 = ClassUtils.isFunctionalInterface(null); // returns false
664+
* }</pre>
665+
*
666+
* @param type the class to check, may be {@code null}
667+
* @return {@code true} if the specified class is a functional interface, {@code false} otherwise
668+
*/
669+
public static boolean isFunctionalInterface(@Nullable Class<?> type) {
670+
if (!isInterface(type)) {
671+
return false;
672+
}
673+
674+
// Trusted by the Java compiler, if the interface is annotated with @FunctionalInterface, it is a functional interface.
675+
if (type.isAnnotationPresent(FunctionalInterface.class)) {
676+
return true;
677+
}
678+
679+
List<Method> methods = findMethods(type, false, true, method -> {
680+
int modifiers = method.getModifiers();
681+
if (isStatic(modifiers) || isOverridenObjectMethod(method) || isBridge(modifiers) || isSynthetic(modifiers)
682+
|| method.isDefault()) {
683+
return false;
684+
}
685+
return true;
686+
});
687+
688+
return methods.size() == 1;
689+
}
690+
620691
/**
621692
* Checks if the specified class is a primitive type.
622693
* <p>
@@ -1104,7 +1175,7 @@ public static boolean arrayTypeEquals(@Nullable Class<?> oneArrayType, @Nullable
11041175
* </p>
11051176
* <p>
11061177
* Note: This method does <i>not</i> support the "[]" suffix notation for
1107-
* primitive arrays; this is only supported by {@link #forName(String)}.
1178+
* primitive arrays; this is only supported by {@link Class#forName(String)}.
11081179
* </p>
11091180
*
11101181
* <h3>Example Usage</h3>

microsphere-java-core/src/test/java/io/microsphere/util/ClassUtilsTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import io.microsphere.lang.ClassDataRepository;
88
import io.microsphere.test.A;
99
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.params.provider.ValueSource;
1011

1112
import javax.annotation.Nonnull;
1213
import java.io.File;
@@ -20,6 +21,7 @@
2021
import java.util.AbstractMap;
2122
import java.util.Collection;
2223
import java.util.Date;
24+
import java.util.EventListener;
2325
import java.util.List;
2426
import java.util.Map;
2527
import java.util.Map.Entry;
@@ -29,6 +31,7 @@
2931
import java.util.TreeMap;
3032
import java.util.concurrent.TimeUnit;
3133
import java.util.function.BiFunction;
34+
import java.util.function.BinaryOperator;
3235
import java.util.function.Predicate;
3336

3437
import static io.microsphere.AbstractTestCase.createRandomTempFile;
@@ -84,7 +87,9 @@
8487
import static io.microsphere.util.ClassUtils.isDerived;
8588
import static io.microsphere.util.ClassUtils.isEnum;
8689
import static io.microsphere.util.ClassUtils.isFinal;
90+
import static io.microsphere.util.ClassUtils.isFunctionalInterface;
8791
import static io.microsphere.util.ClassUtils.isGeneralClass;
92+
import static io.microsphere.util.ClassUtils.isInterface;
8893
import static io.microsphere.util.ClassUtils.isLambdaClass;
8994
import static io.microsphere.util.ClassUtils.isNumber;
9095
import static io.microsphere.util.ClassUtils.isPrimitive;
@@ -191,6 +196,13 @@ void testIsAbstractClass() {
191196
assertFalse(isAbstractClass(Object.class));
192197
}
193198

199+
@Test
200+
void testIsInterface() {
201+
assertTrue(isInterface(List.class));
202+
assertFalse(isInterface(String.class));
203+
assertFalse(isInterface(null));
204+
}
205+
194206
@Test
195207
void testIsGeneralClass() {
196208
// test null
@@ -248,6 +260,25 @@ void testIsLambdaClass() {
248260
assertFalse(isLambdaClass(null));
249261
}
250262

263+
@Test
264+
void testIsFunctionalInterface() {
265+
assertFalse(isFunctionalInterface(null));
266+
assertFalse(isFunctionalInterface(Object.class));
267+
assertFalse(isFunctionalInterface(Serializable.class));
268+
assertFalse(isFunctionalInterface(EventListener.class));
269+
assertFalse(isFunctionalInterface(Collection.class));
270+
// Runnable is annotated with @FunctionalInterface
271+
assertTrue(isFunctionalInterface(Runnable.class));
272+
// Comparable is not annotated with @FunctionalInterface, and it has only one abstract method compareTo(T o)
273+
assertTrue(isFunctionalInterface(Comparable.class));
274+
// Annotation
275+
assertFalse(isFunctionalInterface(ValueSource.class));
276+
277+
assertTrue(isFunctionalInterface(StringDescriptive.class));
278+
//
279+
assertFalse(isFunctionalInterface(Calculator.class));
280+
}
281+
251282
@Test
252283
void testIsPrimitive() {
253284
assertTrue(isPrimitive(void.class));
@@ -993,4 +1024,27 @@ private void assertFindClassNamesInClassPath(URL location) {
9931024
Set<String> classNamesInClassPath = findClassNamesInClassPath(path, true);
9941025
assertFalse(classNamesInClassPath.isEmpty());
9951026
}
1027+
1028+
interface Descriptive {
1029+
1030+
CharSequence getDescription();
1031+
}
1032+
1033+
interface StringDescriptive extends Descriptive {
1034+
1035+
@Override
1036+
String getDescription();
1037+
1038+
static String name() {
1039+
return "StringDescriptive";
1040+
}
1041+
}
1042+
1043+
interface Calculator {
1044+
// A default method containing a lambda expression
1045+
default int getAdder() {
1046+
BinaryOperator<Integer> adder = (a, b) -> a + b;
1047+
return adder.apply(5, 10);
1048+
}
1049+
}
9961050
}

0 commit comments

Comments
 (0)