Run tool tasks from one entry point

- Create scanner which scans for existing tasks
- Unify options management (mostly because multiple Scanner's are a bad idea)
This commit is contained in:
ljacqu 2015-12-12 10:09:38 +01:00
parent a14e3260dc
commit 23d6801baa
6 changed files with 267 additions and 35 deletions

136
src/tools/ToolsRunner.java Normal file
View File

@ -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<String, ToolTask> 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<TaskOption> options = task.getOptions();
Map<String, String> 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<String, ToolTask> 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<String, ToolTask> 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<? 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 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<? extends ToolTask> 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<? extends ToolTask>) clazz
: null;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
private static boolean isInstantiable(Class<?> clazz) {
return !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers());
}
}

View File

@ -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<String, String> options) {
String inputFile = options.get("custom.file");
boolean addMissingKeys = options.get("add.missing.keys").equals("y");
// Set up needed objects
Map<String, String> defaultMessages = null;
@ -69,6 +68,18 @@ public final class MessagesVerifierRunner {
}
}
@Override
public Iterable<TaskOption> 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<String, Boolean> missingKeys = verifier.getMissingKeys();
if (!missingKeys.isEmpty()) {
@ -132,7 +143,6 @@ public final class MessagesVerifierRunner {
}
private static List<File> 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<File> messageFiles = new ArrayList<>();
for (File file : files) {
if (messageFilePattern.matcher(file.getName()).matches()) {
if (MESSAGE_FILE_PATTERN.matcher(file.getName()).matches()) {
messageFiles.add(file);
}
}

View File

@ -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<String, String> 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<TaskOption> 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);
}
}

View File

@ -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");
}
}
}

View File

@ -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;
}
}

View File

@ -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<String, String> options);
String getTaskName();
Iterable<TaskOption> getOptions();
}