1 package org.djutils.test; 2 3 import java.io.Serializable; 4 import java.util.ArrayList; 5 import java.util.List; 6 7 import io.github.classgraph.ClassGraph; 8 import io.github.classgraph.ScanResult; 9 10 /** 11 * ClassList contains two helper methods that check whether all classes in a package implement a given interface or method. 12 * <p> 13 * Copyright (c) 2025-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See 14 * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is 15 * distributed under a three-clause BSD-style license, which can be found at 16 * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>. 17 * </p> 18 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a> 19 * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a> 20 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a> 21 */ 22 public final class ClassList 23 { 24 /** */ 25 private ClassList() 26 { 27 // 28 } 29 30 /** 31 * Return a list with the class names without an explicitly declared method. By default, the check ignores anonymous inner 32 * classes, but includes explicit local or static inner classes. It only looks at classes, not at interfaces, records, 33 * annotation classes or enums. 34 * @param methodName the method to check for 35 * @param packageNameList a list of package names to check 36 * @return a list of classes within the given packages without a toString() method 37 */ 38 public static List<String> classesWithoutMethod(final String methodName, final String... packageNameList) 39 { 40 List<String> result = new ArrayList<>(); 41 try (ScanResult scanResult = new ClassGraph() // .verbose() logs activities 42 .overrideClasspath("target/classes") // don't include test classes 43 .enableAllInfo() // Scan classes, methods, fields, annotations 44 .acceptPackages(packageNameList) // Scan what's in pkg and subpackages (omit to scan all packages) 45 .scan()) 46 { 47 scanResult.getAllClasses() 48 .stream() 49 .filter(ci -> !ci.isInterface() && !ci.isEnum() && !ci.isAnnotation() && !ci.isRecord()) 50 .filter(ci -> !ci.isAnonymousInnerClass()) 51 .filter(ci -> !ci.hasDeclaredMethod(methodName)) 52 .forEach(classInfo -> 53 { result.add(classInfo.getName()); }); 54 } 55 return result; 56 } 57 58 /** 59 * Prints a list with the class names without an explicitly declared method. By default, the check ignores anonymous inner 60 * classes, but includes explicit local or static inner classes. It only looks at classes, not at interfaces, records, 61 * annotation classes or enums. 62 * @param methodName the method to check for 63 * @param packageNameList a list of package names to check 64 */ 65 public static void printClassesWithoutMethod(final String methodName, final String... packageNameList) 66 { 67 System.out.println("Classes without toString() method:"); 68 classesWithoutMethod(methodName, packageNameList).forEach(System.out::println); 69 } 70 71 /** 72 * Return a list with the class names that do not implement the given interface. By default, the check ignores anonymous 73 * inner classes, but includes explicit local or static inner classes. It only looks at classes, not at interfaces, records, 74 * annotation classes or enums. 75 * @param interfaceClass the interface to check for 76 * @param packageNameList a list of package names to check 77 * @return a list of classes within the given packages that do not implement the provided interface 78 */ 79 public static List<String> classesWithoutInterface(final Class<?> interfaceClass, final String... packageNameList) 80 { 81 List<String> result = new ArrayList<>(); 82 try (ScanResult scanResult = new ClassGraph() // .verbose() logs activities 83 .overrideClasspath("target/classes") // don't include test classes 84 .enableAllInfo() // Scan classes, methods, fields, annotations 85 .acceptPackages(packageNameList) // Scan what's in pkg and subpackages (omit to scan all packages) 86 .scan()) 87 { 88 scanResult.getAllClasses() 89 .stream() 90 .filter(ci -> !ci.isInterface() && !ci.isEnum() && !ci.isAnnotation() && !ci.isRecord()) 91 .filter(ci -> !ci.isAnonymousInnerClass()) 92 .filter(ci -> !ci.implementsInterface(interfaceClass)) 93 .forEach(classInfo -> 94 { result.add(classInfo.getName()); }); 95 } 96 return result; 97 } 98 99 /** 100 * Prints a list with the class names that do not implement the given interface. By default, the check ignores anonymous 101 * inner classes, but includes explicit local or static inner classes. It only looks at classes, not at interfaces, records, 102 * annotation classes or enums. 103 * @param interfaceClass the interface to check for 104 * @param packageNameList a list of package names to check 105 */ 106 public static void printClassesWithoutInterface(final Class<?> interfaceClass, final String... packageNameList) 107 { 108 System.out.println("Classes without interface " + interfaceClass.getName()); 109 classesWithoutInterface(interfaceClass, packageNameList).forEach(System.out::println); 110 } 111 112 /** 113 * Walk the path names and make two lists: one of the classes that do not implement toString() and one of the classes that 114 * do not implement the interface Serializable. 115 * @param args can contain the package name(s) to inspect; org.djutils will be taken if the args are empty 116 */ 117 public static void main(final String... args) 118 { 119 printClassesWithoutMethod("toString", args.length > 0 ? args : new String[] {"org.djutils"}); 120 System.out.println(); 121 printClassesWithoutInterface(Serializable.class, args.length > 0 ? args : new String[] {"org.djutils"}); 122 } 123 124 }