mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2024-12-27 19:17:36 +01:00
#932 Create class collector and use it where applicable
- Extract logic for walking through a directory and loading its classes into a separate class - Replace all implementations with the new ClassCollector
This commit is contained in:
parent
f63871600a
commit
10493a3fa3
165
src/test/java/fr/xephi/authme/ClassCollector.java
Normal file
165
src/test/java/fr/xephi/authme/ClassCollector.java
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
package fr.xephi.authme;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects available classes by walking through a source directory.
|
||||||
|
* <p>
|
||||||
|
* This is a naive, zero dependency collector that walks through a file directory
|
||||||
|
* and loads classes from the class loader based on the .java files it encounters.
|
||||||
|
* This is a very slow approach and should be avoided for production code.
|
||||||
|
* <p>
|
||||||
|
* For more performant approaches, see e.g. <a href="https://github.com/ronmamo/reflections">org.reflections</a>.
|
||||||
|
*/
|
||||||
|
public class ClassCollector {
|
||||||
|
|
||||||
|
private final String root;
|
||||||
|
private final String nonCodePath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor. The arguments make up the path from which the collector will start scanning.
|
||||||
|
*
|
||||||
|
* @param nonCodePath beginning of the starting path that are not Java packages, e.g. {@code src/main/java/}
|
||||||
|
* @param packagePath folders following {@code nonCodePath} that are packages, e.g. {@code com/project/app}
|
||||||
|
*/
|
||||||
|
public ClassCollector(String nonCodePath, String packagePath) {
|
||||||
|
if (!nonCodePath.endsWith("/") && !nonCodePath.endsWith("\\")) {
|
||||||
|
nonCodePath = nonCodePath.concat(File.separator);
|
||||||
|
}
|
||||||
|
this.root = nonCodePath + packagePath;
|
||||||
|
this.nonCodePath = nonCodePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects all classes from the parent folder and below.
|
||||||
|
*
|
||||||
|
* @return all classes
|
||||||
|
*/
|
||||||
|
public List<Class<?>> collectClasses() {
|
||||||
|
return collectClasses(x -> true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects all classes from the parent folder and below which are of type {@link T}.
|
||||||
|
*
|
||||||
|
* @param parent the parent which classes need to extend (or be equal to) in order to be collected
|
||||||
|
* @param <T> the parent type
|
||||||
|
* @return list of matching classes
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> List<Class<? extends T>> collectClasses(Class<T> parent) {
|
||||||
|
List<Class<?>> classes = collectClasses(parent::isAssignableFrom);
|
||||||
|
return new ArrayList<>((List) classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects all classes from the parent folder and below which match the given predicate.
|
||||||
|
*
|
||||||
|
* @param filter the predicate classes need to satisfy in order to be collected
|
||||||
|
* @return list of matching classes
|
||||||
|
*/
|
||||||
|
public List<Class<?>> collectClasses(Predicate<Class<?>> filter) {
|
||||||
|
File rootFolder = new File(root);
|
||||||
|
List<Class<?>> collection = new ArrayList<>();
|
||||||
|
collectClasses(rootFolder, filter, collection);
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance of all classes which are of the provided type {@code clazz}.
|
||||||
|
* This method assumes that every class has an accessible no-args constructor for creation.
|
||||||
|
*
|
||||||
|
* @param parent the parent which classes need to extend (or be equal to) in order to be instantiated
|
||||||
|
* @param <T> the parent type
|
||||||
|
* @return collection of created objects
|
||||||
|
*/
|
||||||
|
public <T> List<T> getInstancesOfType(Class<T> parent) {
|
||||||
|
return getInstancesOfType(parent, (clz) -> {
|
||||||
|
try {
|
||||||
|
return canInstantiate(clz) ? clz.newInstance() : null;
|
||||||
|
} catch (InstantiationException | IllegalAccessException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance of all classes which are of the provided type {@code clazz}
|
||||||
|
* with the provided {@code instantiator}.
|
||||||
|
*
|
||||||
|
* @param parent the parent which classes need to extend (or be equal to) in order to be instantiated
|
||||||
|
* @param instantiator function which returns an object of the given class, or null to skip the class
|
||||||
|
* @param <T> the parent type
|
||||||
|
* @return collection of created objects
|
||||||
|
*/
|
||||||
|
public <T> List<T> getInstancesOfType(Class<T> parent, Function<Class<? extends T>, T> instantiator) {
|
||||||
|
return collectClasses(parent)
|
||||||
|
.stream()
|
||||||
|
.map(instantiator)
|
||||||
|
.filter(o -> o != null)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the given class can be instantiated, i.e. if it is not abstract, an interface, etc.
|
||||||
|
*
|
||||||
|
* @param clazz the class to process
|
||||||
|
* @return true if the class can be instantiated, false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean canInstantiate(Class<?> clazz) {
|
||||||
|
return clazz != null && !clazz.isEnum() && !clazz.isInterface()
|
||||||
|
&& !clazz.isArray() && !Modifier.isAbstract(clazz.getModifiers());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively collects the classes based on the files in the directory and in its child directories.
|
||||||
|
*
|
||||||
|
* @param folder the folder to scan
|
||||||
|
* @param filter the class predicate
|
||||||
|
* @param collection collection to add classes to
|
||||||
|
*/
|
||||||
|
private void collectClasses(File folder, Predicate<Class<?>> filter, List<Class<?>> collection) {
|
||||||
|
File[] files = folder.listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
throw new IllegalStateException("Could not read files from '" + folder + "'");
|
||||||
|
}
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
collectClasses(file, filter, collection);
|
||||||
|
} else if (file.isFile()) {
|
||||||
|
Class<?> clazz = loadTaskClassFromFile(file);
|
||||||
|
if (clazz != null && filter.test(clazz)) {
|
||||||
|
collection.add(clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a class from the class loader based on the given file.
|
||||||
|
*
|
||||||
|
* @param file the file whose corresponding Java class should be retrieved
|
||||||
|
* @return the corresponding class, or null if not applicable
|
||||||
|
*/
|
||||||
|
private Class<?> loadTaskClassFromFile(File file) {
|
||||||
|
if (!file.getName().endsWith(".java")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String filePath = file.getPath();
|
||||||
|
String className = filePath
|
||||||
|
.substring(nonCodePath.length(), filePath.length() - 5)
|
||||||
|
.replace(File.separator, ".");
|
||||||
|
try {
|
||||||
|
return Class.forName(className);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,8 @@ import static org.mockito.Mockito.verify;
|
|||||||
*/
|
*/
|
||||||
public final class TestHelper {
|
public final class TestHelper {
|
||||||
|
|
||||||
|
public static final String SOURCES_FOLDER = "src/main/java/";
|
||||||
|
public static final String TEST_SOURCES_FOLDER = "src/test/java/";
|
||||||
public static final String PROJECT_ROOT = "/fr/xephi/authme/";
|
public static final String PROJECT_ROOT = "/fr/xephi/authme/";
|
||||||
|
|
||||||
private TestHelper() {
|
private TestHelper() {
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
package fr.xephi.authme.events;
|
package fr.xephi.authme.events;
|
||||||
|
|
||||||
|
import fr.xephi.authme.ClassCollector;
|
||||||
|
import fr.xephi.authme.TestHelper;
|
||||||
import org.apache.commons.lang.reflect.MethodUtils;
|
import org.apache.commons.lang.reflect.MethodUtils;
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
@ -20,25 +20,14 @@ import static org.junit.Assert.assertThat;
|
|||||||
*/
|
*/
|
||||||
public class EventsConsistencyTest {
|
public class EventsConsistencyTest {
|
||||||
|
|
||||||
private static final String SRC_FOLDER = "src/main/java/";
|
private static final String EVENTS_FOLDER = TestHelper.PROJECT_ROOT + "events/";
|
||||||
private static final String EVENTS_FOLDER = SRC_FOLDER + "/fr/xephi/authme/events/";
|
|
||||||
private static List<Class<? extends Event>> classes;
|
private static List<Class<? extends Event>> classes;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void scanEventClasses() {
|
public static void scanEventClasses() {
|
||||||
File eventsFolder = new File(EVENTS_FOLDER);
|
ClassCollector classCollector = new ClassCollector(TestHelper.SOURCES_FOLDER, EVENTS_FOLDER);
|
||||||
File[] filesInFolder = eventsFolder.listFiles();
|
classes = classCollector.collectClasses(Event.class);
|
||||||
if (filesInFolder == null || filesInFolder.length == 0) {
|
|
||||||
throw new IllegalStateException("Could not read folder '" + EVENTS_FOLDER + "'. Is it correct?");
|
|
||||||
}
|
|
||||||
|
|
||||||
classes = new ArrayList<>();
|
|
||||||
for (File file : filesInFolder) {
|
|
||||||
Class<? extends Event> clazz = getEventClassFromFile(file);
|
|
||||||
if (clazz != null) {
|
|
||||||
classes.add(clazz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (classes.isEmpty()) {
|
if (classes.isEmpty()) {
|
||||||
throw new IllegalStateException("Did not find any AuthMe event classes. Is the folder correct?");
|
throw new IllegalStateException("Did not find any AuthMe event classes. Is the folder correct?");
|
||||||
}
|
}
|
||||||
@ -74,22 +63,4 @@ public class EventsConsistencyTest {
|
|||||||
private static boolean canBeInstantiated(Class<?> clazz) {
|
private static boolean canBeInstantiated(Class<?> clazz) {
|
||||||
return !clazz.isInterface() && !clazz.isEnum() && !Modifier.isAbstract(clazz.getModifiers());
|
return !clazz.isInterface() && !clazz.isEnum() && !Modifier.isAbstract(clazz.getModifiers());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static Class<? extends Event> getEventClassFromFile(File file) {
|
|
||||||
String fileName = file.getPath();
|
|
||||||
String className = fileName
|
|
||||||
.substring(SRC_FOLDER.length(), fileName.length() - ".java".length())
|
|
||||||
.replace(File.separator, ".");
|
|
||||||
try {
|
|
||||||
Class<?> clazz = EventsConsistencyTest.class.getClassLoader().loadClass(className);
|
|
||||||
if (Event.class.isAssignableFrom(clazz)) {
|
|
||||||
return (Class<? extends Event>) clazz;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
throw new IllegalStateException("Could not load class '" + className + "'", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,14 @@ package fr.xephi.authme.settings.properties;
|
|||||||
|
|
||||||
import com.github.authme.configme.SettingsHolder;
|
import com.github.authme.configme.SettingsHolder;
|
||||||
import com.github.authme.configme.properties.Property;
|
import com.github.authme.configme.properties.Property;
|
||||||
|
import fr.xephi.authme.ClassCollector;
|
||||||
import fr.xephi.authme.ReflectionTestUtils;
|
import fr.xephi.authme.ReflectionTestUtils;
|
||||||
import fr.xephi.authme.TestHelper;
|
import fr.xephi.authme.TestHelper;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -24,25 +23,14 @@ import static org.junit.Assert.fail;
|
|||||||
*/
|
*/
|
||||||
public class SettingsClassConsistencyTest {
|
public class SettingsClassConsistencyTest {
|
||||||
|
|
||||||
private static final String SETTINGS_FOLDER = "src/main/java/fr/xephi/authme/settings/properties";
|
private static final String SETTINGS_FOLDER = TestHelper.PROJECT_ROOT + "settings/properties";
|
||||||
private static List<Class<? extends SettingsHolder>> classes;
|
private static List<Class<? extends SettingsHolder>> classes;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void scanForSettingsClasses() {
|
public static void scanForSettingsClasses() {
|
||||||
File settingsFolder = new File(SETTINGS_FOLDER);
|
ClassCollector collector = new ClassCollector(TestHelper.SOURCES_FOLDER, SETTINGS_FOLDER);
|
||||||
File[] filesInFolder = settingsFolder.listFiles();
|
classes = collector.collectClasses(SettingsHolder.class);
|
||||||
if (filesInFolder == null || filesInFolder.length == 0) {
|
System.out.println("Found " + classes.size() + " SettingsHolder implementations");
|
||||||
throw new IllegalStateException("Could not read folder '" + SETTINGS_FOLDER + "'. Is it correct?");
|
|
||||||
}
|
|
||||||
|
|
||||||
classes = new ArrayList<>();
|
|
||||||
for (File file : filesInFolder) {
|
|
||||||
Class<? extends SettingsHolder> clazz = getSettingsClassFromFile(file);
|
|
||||||
if (clazz != null) {
|
|
||||||
classes.add(clazz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
System.out.println("Found " + classes.size() + " SettingsClass implementations");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,22 +81,4 @@ public class SettingsClassConsistencyTest {
|
|||||||
int modifiers = field.getModifiers();
|
int modifiers = field.getModifiers();
|
||||||
return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers);
|
return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static Class<? extends SettingsHolder> getSettingsClassFromFile(File file) {
|
|
||||||
String fileName = file.getPath();
|
|
||||||
String className = fileName
|
|
||||||
.substring("src/main/java/".length(), fileName.length() - ".java".length())
|
|
||||||
.replace(File.separator, ".");
|
|
||||||
try {
|
|
||||||
Class<?> clazz = SettingsClassConsistencyTest.class.getClassLoader().loadClass(className);
|
|
||||||
if (SettingsHolder.class.isAssignableFrom(clazz)) {
|
|
||||||
return (Class<? extends SettingsHolder>) clazz;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
throw new IllegalStateException("Could not load class '" + className + "'", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
package tools;
|
package tools;
|
||||||
|
|
||||||
|
import fr.xephi.authme.ClassCollector;
|
||||||
|
import fr.xephi.authme.TestHelper;
|
||||||
import tools.utils.AutoToolTask;
|
import tools.utils.AutoToolTask;
|
||||||
import tools.utils.ToolTask;
|
import tools.utils.ToolTask;
|
||||||
import tools.utils.ToolsConstants;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.util.HashMap;
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runner for executing tool tasks.
|
* Runner for executing tool tasks.
|
||||||
*/
|
*/
|
||||||
public final class ToolsRunner {
|
public final class ToolsRunner {
|
||||||
|
|
||||||
private ToolsRunner() {
|
private Map<String, ToolTask> tasks;
|
||||||
|
|
||||||
|
private ToolsRunner(Map<String, ToolTask> tasks) {
|
||||||
|
this.tasks = tasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,19 +26,23 @@ public final class ToolsRunner {
|
|||||||
* @param args .
|
* @param args .
|
||||||
*/
|
*/
|
||||||
public static void main(String... args) {
|
public static void main(String... args) {
|
||||||
// Collect tasks and show them
|
// Note ljacqu 20151212: If the tools folder becomes a lot bigger, it will make sense to restrict the depth
|
||||||
File toolsFolder = new File(ToolsConstants.TOOLS_SOURCE_ROOT);
|
// of this recursive collector
|
||||||
Map<String, ToolTask> tasks = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
ClassCollector collector = new ClassCollector(TestHelper.TEST_SOURCES_FOLDER, "tools");
|
||||||
collectTasksInDirectory(toolsFolder, tasks);
|
Map<String, ToolTask> tasks = new HashMap<>();
|
||||||
|
for (ToolTask task : collector.getInstancesOfType(ToolTask.class)) {
|
||||||
|
tasks.put(task.getTaskName(), task);
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolsRunner runner = new ToolsRunner(tasks);
|
||||||
if (args == null || args.length == 0) {
|
if (args == null || args.length == 0) {
|
||||||
promptAndExecuteTask(tasks);
|
runner.promptAndExecuteTask();
|
||||||
} else {
|
} else {
|
||||||
executeAutomaticTasks(tasks, args);
|
runner.executeAutomaticTasks(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void promptAndExecuteTask(Map<String, ToolTask> tasks) {
|
private void promptAndExecuteTask() {
|
||||||
System.out.println("The following tasks are available:");
|
System.out.println("The following tasks are available:");
|
||||||
for (String key : tasks.keySet()) {
|
for (String key : tasks.keySet()) {
|
||||||
System.out.println("- " + key);
|
System.out.println("- " + key);
|
||||||
@ -57,7 +61,7 @@ public final class ToolsRunner {
|
|||||||
scanner.close();
|
scanner.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void executeAutomaticTasks(Map<String, ToolTask> tasks, String... requests) {
|
private void executeAutomaticTasks(String... requests) {
|
||||||
for (String taskName : requests) {
|
for (String taskName : requests) {
|
||||||
ToolTask task = tasks.get(taskName);
|
ToolTask task = tasks.get(taskName);
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
@ -69,80 +73,4 @@ public final class ToolsRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add all implementations of {@link ToolTask} from the given folder to the provided collection.
|
|
||||||
*
|
|
||||||
* @param dir The directory to scan
|
|
||||||
* @param taskCollection The collection to add results to
|
|
||||||
*/
|
|
||||||
// Note ljacqu 20151212: If the tools folder becomes a lot bigger, it will make sense to restrict the depth
|
|
||||||
// of this recursive collector
|
|
||||||
private static void collectTasksInDirectory(File dir, Map<String, ToolTask> taskCollection) {
|
|
||||||
File[] files = dir.listFiles();
|
|
||||||
if (files == null) {
|
|
||||||
throw new IllegalStateException("Cannot read folder '" + dir + "'");
|
|
||||||
}
|
|
||||||
for (File file : files) {
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
collectTasksInDirectory(file, taskCollection);
|
|
||||||
} else if (file.isFile()) {
|
|
||||||
ToolTask task = getTaskFromFile(file);
|
|
||||||
if (task != null) {
|
|
||||||
taskCollection.put(task.getTaskName(), task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a {@link ToolTask} instance defined by the given source file.
|
|
||||||
*
|
|
||||||
* @param file The file to load
|
|
||||||
* @return ToolTask instance, or null if not applicable
|
|
||||||
*/
|
|
||||||
private static ToolTask getTaskFromFile(File file) {
|
|
||||||
Class<? extends ToolTask> taskClass = loadTaskClassFromFile(file);
|
|
||||||
if (taskClass == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Constructor<? extends ToolTask> constructor = taskClass.getConstructor();
|
|
||||||
return constructor.newInstance();
|
|
||||||
} catch (NoSuchMethodException | InvocationTargetException |
|
|
||||||
IllegalAccessException | InstantiationException e) {
|
|
||||||
throw new IllegalStateException("Cannot instantiate task '" + taskClass + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the class the file defines if it implements {@link ToolTask}.
|
|
||||||
*
|
|
||||||
* @return The class instance, or null if not applicable
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static Class<? extends ToolTask> loadTaskClassFromFile(File file) {
|
|
||||||
if (!file.getName().endsWith(".java")) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String filePath = file.getPath();
|
|
||||||
String className = "tools." + filePath
|
|
||||||
.substring(ToolsConstants.TOOLS_SOURCE_ROOT.length(), filePath.length() - 5)
|
|
||||||
.replace(File.separator, ".");
|
|
||||||
try {
|
|
||||||
Class<?> clazz = ToolsRunner.class.getClassLoader().loadClass(className);
|
|
||||||
return ToolTask.class.isAssignableFrom(clazz) && isInstantiable(clazz)
|
|
||||||
? (Class<? extends ToolTask>) clazz
|
|
||||||
: null;
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isInstantiable(Class<?> clazz) {
|
|
||||||
return !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
package tools.checktestmocks;
|
package tools.checktestmocks;
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
|
||||||
import com.google.common.collect.Collections2;
|
import com.google.common.collect.Collections2;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
import fr.xephi.authme.ClassCollector;
|
||||||
|
import fr.xephi.authme.TestHelper;
|
||||||
import fr.xephi.authme.util.StringUtils;
|
import fr.xephi.authme.util.StringUtils;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import tools.utils.AutoToolTask;
|
import tools.utils.AutoToolTask;
|
||||||
import tools.utils.InjectorUtils;
|
import tools.utils.InjectorUtils;
|
||||||
import tools.utils.ToolsConstants;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -38,33 +37,13 @@ public class CheckTestMocks implements AutoToolTask {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void executeDefault() {
|
public void executeDefault() {
|
||||||
readAndCheckFiles(new File(ToolsConstants.TEST_SOURCE_ROOT));
|
ClassCollector collector = new ClassCollector(TestHelper.SOURCES_FOLDER, TestHelper.PROJECT_ROOT);
|
||||||
|
for (Class<?> clazz : collector.collectClasses(c -> isTestClassWithMocks(c))) {
|
||||||
|
checkClass(clazz);
|
||||||
|
}
|
||||||
System.out.println(StringUtils.join("\n", errors));
|
System.out.println(StringUtils.join("\n", errors));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively reads directories and checks the contained classes.
|
|
||||||
*
|
|
||||||
* @param dir the directory to read
|
|
||||||
*/
|
|
||||||
private void readAndCheckFiles(File dir) {
|
|
||||||
File[] files = dir.listFiles();
|
|
||||||
if (files == null) {
|
|
||||||
throw new IllegalStateException("Cannot read folder '" + dir + "'");
|
|
||||||
}
|
|
||||||
for (File file : files) {
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
readAndCheckFiles(file);
|
|
||||||
} else if (file.isFile()) {
|
|
||||||
Class<?> clazz = loadTestClass(file);
|
|
||||||
if (clazz != null) {
|
|
||||||
checkClass(clazz);
|
|
||||||
}
|
|
||||||
// else System.out.format("No @Mock fields found in class of file '%s'%n", file.getName())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the given test class' @Mock fields against the corresponding production class' @Inject fields.
|
* Checks the given test class' @Mock fields against the corresponding production class' @Inject fields.
|
||||||
*
|
*
|
||||||
@ -91,20 +70,6 @@ public class CheckTestMocks implements AutoToolTask {
|
|||||||
errors.add(clazz.getSimpleName() + ": " + message);
|
errors.add(clazz.getSimpleName() + ": " + message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Class<?> loadTestClass(File file) {
|
|
||||||
String fileName = file.getPath();
|
|
||||||
String className = fileName
|
|
||||||
// Strip source folders and .java ending
|
|
||||||
.substring("src/test/java/".length(), fileName.length() - 5)
|
|
||||||
.replace(File.separator, ".");
|
|
||||||
try {
|
|
||||||
Class<?> clazz = CheckTestMocks.class.getClassLoader().loadClass(className);
|
|
||||||
return isTestClassWithMocks(clazz) ? clazz : null;
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
throw new UnsupportedOperationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Set<Class<?>> getMocks(Class<?> clazz) {
|
private static Set<Class<?>> getMocks(Class<?> clazz) {
|
||||||
Set<Class<?>> result = new HashSet<>();
|
Set<Class<?>> result = new HashSet<>();
|
||||||
for (Field field : clazz.getDeclaredFields()) {
|
for (Field field : clazz.getDeclaredFields()) {
|
||||||
@ -147,12 +112,7 @@ public class CheckTestMocks implements AutoToolTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String formatClassList(Collection<Class<?>> coll) {
|
private static String formatClassList(Collection<Class<?>> coll) {
|
||||||
Collection<String> classNames = Collections2.transform(coll, new Function<Class<?>, String>() {
|
Collection<String> classNames = Collections2.transform(coll, Class::getSimpleName);
|
||||||
@Override
|
|
||||||
public String apply(Class<?> input) {
|
|
||||||
return input.getSimpleName();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return StringUtils.join(", ", classNames);
|
return StringUtils.join(", ", classNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ import ch.jalu.injector.handlers.instantiation.Instantiation;
|
|||||||
import com.google.common.collect.HashMultimap;
|
import com.google.common.collect.HashMultimap;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
import fr.xephi.authme.ClassCollector;
|
||||||
|
import fr.xephi.authme.TestHelper;
|
||||||
import fr.xephi.authme.command.ExecutableCommand;
|
import fr.xephi.authme.command.ExecutableCommand;
|
||||||
import fr.xephi.authme.converter.Converter;
|
import fr.xephi.authme.converter.Converter;
|
||||||
import fr.xephi.authme.initialization.DataFolder;
|
import fr.xephi.authme.initialization.DataFolder;
|
||||||
@ -16,7 +18,6 @@ import tools.utils.InjectorUtils;
|
|||||||
import tools.utils.ToolTask;
|
import tools.utils.ToolTask;
|
||||||
import tools.utils.ToolsConstants;
|
import tools.utils.ToolsConstants;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -34,15 +35,12 @@ import java.util.Scanner;
|
|||||||
public class DrawDependency implements ToolTask {
|
public class DrawDependency implements ToolTask {
|
||||||
|
|
||||||
private static final String DOT_FILE = ToolsConstants.TOOLS_SOURCE_ROOT + "dependencygraph/graph.dot";
|
private static final String DOT_FILE = ToolsConstants.TOOLS_SOURCE_ROOT + "dependencygraph/graph.dot";
|
||||||
// Package root
|
|
||||||
private static final String ROOT_PACKAGE = "fr.xephi.authme";
|
|
||||||
|
|
||||||
private static final List<Class<?>> SUPER_TYPES = ImmutableList.of(ExecutableCommand.class,
|
private static final List<Class<?>> SUPER_TYPES = ImmutableList.of(ExecutableCommand.class,
|
||||||
SynchronousProcess.class, AsynchronousProcess.class, EncryptionMethod.class, Converter.class, Listener.class);
|
SynchronousProcess.class, AsynchronousProcess.class, EncryptionMethod.class, Converter.class, Listener.class);
|
||||||
|
|
||||||
/** Annotation types by which dependencies are identified. */
|
/** Annotation types by which dependencies are identified. */
|
||||||
private static final List<Class<? extends Annotation>> ANNOTATION_TYPES =
|
private static final List<Class<? extends Annotation>> ANNOTATION_TYPES = ImmutableList.of(DataFolder.class);
|
||||||
ImmutableList.<Class<? extends Annotation>>of(DataFolder.class);
|
|
||||||
|
|
||||||
private boolean mapToSupertype;
|
private boolean mapToSupertype;
|
||||||
// Map with the graph's nodes: value is one of the key's dependencies
|
// Map with the graph's nodes: value is one of the key's dependencies
|
||||||
@ -59,7 +57,10 @@ public class DrawDependency implements ToolTask {
|
|||||||
mapToSupertype = "y".equalsIgnoreCase(scanner.nextLine());
|
mapToSupertype = "y".equalsIgnoreCase(scanner.nextLine());
|
||||||
|
|
||||||
// Gather all connections
|
// Gather all connections
|
||||||
readAndProcessFiles(new File(ToolsConstants.MAIN_SOURCE_ROOT));
|
ClassCollector collector = new ClassCollector(TestHelper.SOURCES_FOLDER, TestHelper.PROJECT_ROOT);
|
||||||
|
for (Class<?> clazz : collector.collectClasses()) {
|
||||||
|
processClass(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
// Prompt user for simplification of graph
|
// Prompt user for simplification of graph
|
||||||
System.out.println("Do you want to remove classes that are not used as dependency elsewhere?");
|
System.out.println("Do you want to remove classes that are not used as dependency elsewhere?");
|
||||||
@ -93,28 +94,6 @@ public class DrawDependency implements ToolTask {
|
|||||||
System.out.format("Run 'dot -Tpng %s -o graph.png' to generate image (requires GraphViz)%n", DOT_FILE);
|
System.out.format("Run 'dot -Tpng %s -o graph.png' to generate image (requires GraphViz)%n", DOT_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively reads the given directory and processes the files.
|
|
||||||
*
|
|
||||||
* @param dir the directory to read
|
|
||||||
*/
|
|
||||||
private void readAndProcessFiles(File dir) {
|
|
||||||
File[] files = dir.listFiles();
|
|
||||||
if (files == null) {
|
|
||||||
throw new IllegalStateException("Cannot read folder '" + dir + "'");
|
|
||||||
}
|
|
||||||
for (File file : files) {
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
readAndProcessFiles(file);
|
|
||||||
} else if (file.isFile()) {
|
|
||||||
Class<?> clazz = loadClass(file);
|
|
||||||
if (clazz != null) {
|
|
||||||
processClass(clazz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processClass(Class<?> clazz) {
|
private void processClass(Class<?> clazz) {
|
||||||
List<String> dependencies = getDependencies(clazz);
|
List<String> dependencies = getDependencies(clazz);
|
||||||
if (dependencies != null) {
|
if (dependencies != null) {
|
||||||
@ -134,21 +113,6 @@ public class DrawDependency implements ToolTask {
|
|||||||
return clazz;
|
return clazz;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load Class object for the class in the given file
|
|
||||||
private static Class<?> loadClass(File file) {
|
|
||||||
final String fileName = file.getPath().replace(File.separator, ".");
|
|
||||||
if (!fileName.endsWith(".java")) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final String className = fileName
|
|
||||||
.substring(fileName.indexOf(ROOT_PACKAGE), fileName.length() - ".java".length());
|
|
||||||
try {
|
|
||||||
return DrawDependency.class.getClassLoader().loadClass(className);
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getDependencies(Class<?> clazz) {
|
private List<String> getDependencies(Class<?> clazz) {
|
||||||
Instantiation<?> instantiation = InjectorUtils.getInstantiationMethod(clazz);
|
Instantiation<?> instantiation = InjectorUtils.getInstantiationMethod(clazz);
|
||||||
return instantiation == null ? null : formatInjectionDependencies(instantiation);
|
return instantiation == null ? null : formatInjectionDependencies(instantiation);
|
||||||
|
Loading…
Reference in New Issue
Block a user