diff --git a/src/tools/ToolsRunner.java b/src/tools/ToolsRunner.java new file mode 100644 index 00000000..a0589f1b --- /dev/null +++ b/src/tools/ToolsRunner.java @@ -0,0 +1,136 @@ +import utils.ScannerHelper; +import utils.TaskOption; +import utils.ToolTask; +import utils.ToolsConstants; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +/** + * Main entry point for tool tasks. + */ +public class ToolsRunner { + + private static final String DIR_SEPARATOR = File.separator; + + public static void main(String... args) { + // Collect tasks and show them + File toolsFolder = new File(ToolsConstants.TOOLS_SOURCE_ROOT); + Map tasks = new HashMap<>(); + collectTasksInDirectory(toolsFolder, tasks); + showHelp(tasks); + + // Prompt user for task and handle input + System.out.println("Please enter the task to run:"); + Scanner scanner = new Scanner(System.in); + String inputTask = scanner.nextLine(); + ToolTask task = tasks.get(inputTask); + + if (task != null) { + executeTask(task, scanner); + } else { + System.out.println("Unknown task"); + } + scanner.close(); + } + + /** + * Execute the given tool task after prompting the user for the required options. + * + * @param task The task to run + * @param scanner The scanner instance + */ + private static void executeTask(ToolTask task, Scanner scanner) { + Iterable options = task.getOptions(); + Map inputOptions = new HashMap<>(); + for (TaskOption option : options) { + System.out.println(option.getDescription()); + String input = ScannerHelper.getAnswer(option.getDefaultOption(), scanner, option.getOptions()); + inputOptions.put(option.getName(), input); + } + + task.execute(inputOptions); + } + + private static void showHelp(Map taskCollection) { + System.out.println("The following tasks are available:"); + for (String key : taskCollection.keySet()) { + System.out.println("- " + key); + } + } + + // 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 taskCollection) { + File[] files = dir.listFiles(); + if (files == null) { + throw new RuntimeException("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 from the given file. + * + * @param file The file to load + * @return ToolTask instance or null if not applicable + */ + private static ToolTask getTaskFromFile(File file) { + Class taskClass = loadTaskClassFromFile(file); + if (taskClass == null) { + return null; + } + + try { + Constructor constructor = taskClass.getConstructor(); + return constructor.newInstance(); + } catch (NoSuchMethodException | InvocationTargetException | + IllegalAccessException | InstantiationException e) { + throw new RuntimeException("Cannot instantiate task '" + taskClass + "'"); + } + } + + /** + * Return the class the file defines if it implements a {@link ToolTask}. + * + * @return The class instance, or null if not applicable + */ + @SuppressWarnings("unchecked") + private static Class loadTaskClassFromFile(File file) { + if (!file.getName().endsWith(".java")) { + return null; + } + + String filePath = file.getPath(); + String className = filePath + .substring(ToolsConstants.TOOLS_SOURCE_ROOT.length(), filePath.length() - 5) + .replace(DIR_SEPARATOR, "."); + try { + Class clazz = ClassLoader.getSystemClassLoader().loadClass(className); + return ToolTask.class.isAssignableFrom(clazz) && isInstantiable(clazz) + ? (Class) clazz + : null; + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + private static boolean isInstantiable(Class clazz) { + return !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()); + } + +} diff --git a/src/tools/messages/MessagesVerifierRunner.java b/src/tools/messages/VerifyMessagesTask.java similarity index 79% rename from src/tools/messages/MessagesVerifierRunner.java rename to src/tools/messages/VerifyMessagesTask.java index 174c00f7..f0fe3533 100644 --- a/src/tools/messages/MessagesVerifierRunner.java +++ b/src/tools/messages/VerifyMessagesTask.java @@ -2,10 +2,13 @@ package messages; import fr.xephi.authme.util.StringUtils; import utils.FileUtils; +import utils.TaskOption; +import utils.ToolTask; import utils.ToolsConstants; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -17,27 +20,23 @@ import java.util.regex.Pattern; import static java.lang.String.format; /** - * Entry point of the messages verifier. + * Task to verify the keys in the messages files. */ -public final class MessagesVerifierRunner { +public final class VerifyMessagesTask implements ToolTask { private static final String MESSAGES_FOLDER = ToolsConstants.MAIN_RESOURCES_ROOT + "messages/"; + private static final Pattern MESSAGE_FILE_PATTERN = Pattern.compile("messages_[a-z]{2,7}\\.yml"); private static final String SOURCES_TAG = "{msgdir}"; - private MessagesVerifierRunner() { + @Override + public String getTaskName() { + return "verifyMessages"; } - public static void main(String[] args) { - // Prompt user for options - Scanner scanner = new Scanner(System.in); - System.out.println("Check a specific file only?"); - System.out.println("- Empty line will check all files in the resources messages folder (default)"); - System.out.println(format("- %s will be replaced to the messages folder %s", SOURCES_TAG, MESSAGES_FOLDER)); - String inputFile = scanner.nextLine(); - - System.out.println("Add any missing keys to files? ['y' = yes]"); - boolean addMissingKeys = "y".equals(scanner.nextLine()); - scanner.close(); + @Override + public void execute(Map options) { + String inputFile = options.get("custom.file"); + boolean addMissingKeys = options.get("add.missing.keys").equals("y"); // Set up needed objects Map defaultMessages = null; @@ -69,6 +68,18 @@ public final class MessagesVerifierRunner { } } + @Override + public Iterable getOptions() { + String customFileDescription = StringUtils.join(System.getProperty("line.separator"), + "Check a specific file only? (optional - enter custom filename)", + "- Empty line will check all files in the resources messages folder", + format("- %s will be replaced to the messages folder %s", SOURCES_TAG, MESSAGES_FOLDER)); + + return Arrays.asList( + new TaskOption("custom.file", customFileDescription, "", null), + new TaskOption("add.missing.keys", "Add missing keys to files? [y/n]", "n", "y", "n")); + } + private static void verifyFile(MessageFileVerifier verifier) { Map missingKeys = verifier.getMissingKeys(); if (!missingKeys.isEmpty()) { @@ -132,7 +143,6 @@ public final class MessagesVerifierRunner { } private static List getMessagesFiles() { - final Pattern messageFilePattern = Pattern.compile("messages_[a-z]{2,7}\\.yml"); File folder = new File(MESSAGES_FOLDER); File[] files = folder.listFiles(); if (files == null) { @@ -141,7 +151,7 @@ public final class MessagesVerifierRunner { List messageFiles = new ArrayList<>(); for (File file : files) { - if (messageFilePattern.matcher(file.getName()).matches()) { + if (MESSAGE_FILE_PATTERN.matcher(file.getName()).matches()) { messageFiles.add(file); } } diff --git a/src/tools/permissions/PermissionsListWriter.java b/src/tools/permissions/PermissionsListWriter.java index c8ba0554..3b1e9c33 100644 --- a/src/tools/permissions/PermissionsListWriter.java +++ b/src/tools/permissions/PermissionsListWriter.java @@ -3,32 +3,32 @@ package permissions; import utils.ANewMap; import utils.FileUtils; import utils.TagReplacer; +import utils.TaskOption; +import utils.ToolTask; import utils.ToolsConstants; +import java.util.Arrays; import java.util.Map; -import java.util.Scanner; import java.util.Set; /** - * Class responsible for formatting a permissions node list and + * Task responsible for formatting a permissions node list and * for writing it to a file if desired. */ -public class PermissionsListWriter { +public class PermissionsListWriter implements ToolTask { private static final String PERMISSIONS_OUTPUT_FILE = ToolsConstants.DOCS_FOLDER + "permission_nodes.md"; - public static void main(String[] args) { - // Ask if result should be written to file - Scanner scanner = new Scanner(System.in); - System.out.println("Include description? [Enter 'n' for no]"); - boolean includeDescription = !matches("n", scanner); + @Override + public String getTaskName() { + return "writePermissionsList"; + } - boolean writeToFile = false; - if (includeDescription) { - System.out.println("Write to file? [Enter 'y' for yes]"); - writeToFile = matches("y", scanner); - } - scanner.close(); + @Override + public void execute(Map options) { + // Ask if result should be written to file + boolean includeDescription = options.get("include.description").equals("y"); + boolean writeToFile = options.get("write.to.file").equals("y"); if (!includeDescription) { outputSimpleList(); @@ -39,6 +39,12 @@ public class PermissionsListWriter { } } + @Override + public Iterable getOptions() { + return Arrays.asList( + new TaskOption("include.description", "Include description? [y/n]", "y", "y", "n"), + new TaskOption("write.to.file", "Write to file? [y/n]", "n", "y", "n")); + } private static void generateAndWriteFile() { final String permissionsTagValue = generatePermissionsList(); @@ -77,9 +83,4 @@ public class PermissionsListWriter { System.out.println("Total: " + nodes.size()); } - private static boolean matches(String answer, Scanner sc) { - String userInput = sc.nextLine(); - return answer.equalsIgnoreCase(userInput); - } - } diff --git a/src/tools/utils/ScannerHelper.java b/src/tools/utils/ScannerHelper.java new file mode 100644 index 00000000..b7741164 --- /dev/null +++ b/src/tools/utils/ScannerHelper.java @@ -0,0 +1,33 @@ +package utils; + +import fr.xephi.authme.util.StringUtils; + +import java.util.Scanner; + +/** + * Helper class for retrieving an answer from a Scanner. + */ +public class ScannerHelper { + + // def may be null to force the selection of one of the options + // options may be null to just select whatever comes in + public static String getAnswer(String def, Scanner scanner, String... options) { + while (true) { + String input = scanner.nextLine(); + if (StringUtils.isEmpty(input) && def != null) { + return def; + } + + if (options == null) { + return input; + } else { + for (String option : options) { + if (input.equals(option)) { + return option; + } + } + } + System.out.println("Invalid answer, please try again"); + } + } +} diff --git a/src/tools/utils/TaskOption.java b/src/tools/utils/TaskOption.java new file mode 100644 index 00000000..ad5acdd6 --- /dev/null +++ b/src/tools/utils/TaskOption.java @@ -0,0 +1,35 @@ +package utils; + +/** + * Option required by a tool task. + */ +public class TaskOption { + + private final String name; + private final String description; + private final String defaultOption; + private final String[] options; + + public TaskOption(String name, String description, String defaultOption, String... options) { + this.name = name; + this.description = description; + this.defaultOption = defaultOption; + this.options = options; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getDefaultOption() { + return defaultOption; + } + + public String[] getOptions() { + return options; + } +} diff --git a/src/tools/utils/ToolTask.java b/src/tools/utils/ToolTask.java new file mode 100644 index 00000000..d74fcc3f --- /dev/null +++ b/src/tools/utils/ToolTask.java @@ -0,0 +1,17 @@ +package utils; + +import java.util.Map; + +/** + * Common interface for tool tasks. Note that the implementing tasks are instantiated + * with the default constructor. It is required that it be public. + */ +public interface ToolTask { + + void execute(Map options); + + String getTaskName(); + + Iterable getOptions(); + +}