From fbc5b7d8311872ad5f70ee36e06961509b6502a4 Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Tue, 1 Apr 2025 01:13:34 +0300 Subject: [PATCH] Better documentation. Better scanner. --- .../commons/cli_helper/CLIHelper.java | 270 ++++++++++-------- 1 file changed, 154 insertions(+), 116 deletions(-) diff --git a/src/main/java/eu/svjatoslav/commons/cli_helper/CLIHelper.java b/src/main/java/eu/svjatoslav/commons/cli_helper/CLIHelper.java index 9d574e0..96fdfab 100755 --- a/src/main/java/eu/svjatoslav/commons/cli_helper/CLIHelper.java +++ b/src/main/java/eu/svjatoslav/commons/cli_helper/CLIHelper.java @@ -7,51 +7,76 @@ package eu.svjatoslav.commons.cli_helper; import java.util.Scanner; /** - * Command-line interface helper. + *

+ * A utility class that provides methods for reading and validating various + * data types from the command line. The methods in this class prompt the user + * with a given message, read the user’s input, validate and parse that input, + * and then either return a valid value or re-prompt on invalid input. + *

+ * + *

+ * Each method optionally supports: + *

+ * + * + *

+ * Example usage: + *

+ *
+ * {@code
+ *   Boolean answer = CLIHelper.askBoolean("Do you want to continue?", true);
+ *   Integer number = CLIHelper.askInteger("Enter a number between 5 and 10:", 7, 5, 10, false);
+ *   String name = CLIHelper.askString("What's your name?", "Anonymous", 2, 20, false);
+ * }
+ * 
*/ public class CLIHelper { + /** + * A single Scanner for all input reading, so we aren't creating new Scanners on + * {@link System#in} repeatedly. + */ + private static final Scanner SCANNER = new Scanner(System.in); + /** * Asks the user for a boolean value using the specified prompt on the command line. - * The user may respond with one of the following to indicate "true": - * - * ...or one of the following to indicate "false": + *

+ * Valid “true” responses are: y, yes, true. + * Valid “false” responses are: n, no, false. + *

+ *

+ * If the user presses ENTER (empty input): + *

* - * If the user presses ENTER (empty input), the provided defaultValue is returned. * * @param prompt the message to display to the user - * @param defaultValue the boolean value to return if the user provides no input. If null, user must provide input. - * @return {@code true} if the user answered affirmatively, - * {@code false} if they answered negatively, - * or {@code defaultValue} if input was empty + * @param defaultValue the boolean value to return if the user provides no input (may be {@code null}) + * @param allowEmpty if {@code true}, empty input is acceptable (and method returns {@code null} if no default); otherwise keep asking + * @return {@code true} if the user answered affirmatively, {@code false} if negatively, or the default/empty result as described above */ public static Boolean askBoolean(final String prompt, final Boolean defaultValue, boolean allowEmpty) { while (true) { - // If we have a defaultValue, display it in brackets; otherwise display no default String displayPrompt = prompt + (defaultValue != null ? " [" + defaultValue + "]" : "") + ": "; - // Read user input System.out.print(displayPrompt); - String line = new Scanner(System.in).nextLine().trim(); - // If user just pressed Enter: - if (line.isEmpty()){ + String line = SCANNER.nextLine().trim(); - // If a defaultValue was supplied, return it + // If user just pressed Enter: + if (line.isEmpty()) { if (defaultValue != null) { return defaultValue; } - if (allowEmpty) { return null; } else { @@ -61,59 +86,71 @@ public class CLIHelper { } final String userInput = line.toLowerCase(); - if ("y".equals(userInput) || "yes".equals(userInput) || "true".equals(userInput)) { return true; } if ("n".equals(userInput) || "no".equals(userInput) || "false".equals(userInput)) { return false; } - System.out.println("Invalid input. Please enter y/yes/true or n/no/false."); } } + /** + * Convenience method that calls {@link #askBoolean(String, Boolean, boolean)} with {@code allowEmpty = false}. + * + * @param prompt the message to display to the user + * @param defaultValue the boolean value to return if the user provides no input (may be {@code null}) + * @return {@code true} if the user answered affirmatively, {@code false} if negatively, or the default if provided + */ public static Boolean askBoolean(String prompt, Boolean defaultValue) { return askBoolean(prompt, defaultValue, false); } + /** + * Convenience method that calls {@link #askBoolean(String, Boolean, boolean)} with no default and {@code allowEmpty = false}. + * + * @param prompt the message to display to the user + * @return {@code true} if the user answered affirmatively, {@code false} if negatively + * @throws IllegalStateException if the user never provides a valid input + */ public static Boolean askBoolean(String prompt) { return askBoolean(prompt, null, false); } /** * Asks the user for a float value using the specified prompt on the command line. - * The user is prompted until a valid float (within optional ranges) is provided. + *

+ * The user is prompted until a valid float (optionally enforced via {@code min}/{@code max}) + * is provided. If {@code defaultValue} is specified and the user presses Enter, that default is + * returned. If {@code defaultValue} is {@code null} and the user presses Enter, the behavior + * depends on {@code allowEmpty}: + *

* * - * @param prompt The prompt displayed to the user - * @param defaultValue The default float if user simply presses Enter (may be null) - * @param min The minimum acceptable value (inclusive), or null if no lower bound - * @param max The maximum acceptable value (inclusive), or null if no upper bound - * @return A Float value that the user entered, or the defaultValue, or null if no defaultValue was given + * @param prompt the prompt displayed to the user + * @param defaultValue the default float if user simply presses Enter (may be {@code null}) + * @param min the minimum acceptable value (inclusive), or {@code null} if no lower bound + * @param max the maximum acceptable value (inclusive), or {@code null} if no upper bound + * @param allowEmpty if {@code true}, empty input returns {@code null} when {@code defaultValue} is null + * @return a {@code Float} value entered by the user, or {@code defaultValue}, or {@code null} */ public static Float askFloat(String prompt, Float defaultValue, Float min, Float max, boolean allowEmpty) { while (true) { - // If we have a defaultValue, display it in brackets; otherwise display no default String displayPrompt = prompt + (defaultValue != null ? " [" + defaultValue + "]" : "") + ": "; - // Read user input System.out.print(displayPrompt); - String input = new Scanner(System.in).nextLine().trim(); + String input = SCANNER.nextLine().trim(); - if (input.isEmpty()){ - // If a defaultValue was supplied, return it + if (input.isEmpty()) { if (defaultValue != null) { return defaultValue; } - if (allowEmpty) { return null; } else { @@ -122,74 +159,70 @@ public class CLIHelper { } } - - // Parse float value try { float parsedValue = Float.parseFloat(input); - - // Check against min if specified if (min != null && parsedValue < min) { System.out.println("Value must be at least " + min + "."); continue; } - - // Check against max if specified if (max != null && parsedValue > max) { System.out.println("Value must be at most " + max + "."); continue; } - - // Parsed successfully within optional bounds return parsedValue; - } catch (NumberFormatException e) { System.out.println("Invalid number format. Try again."); } } } + /** + * Convenience method for {@link #askFloat(String, Float, Float, Float, boolean)} with no min/max range and {@code allowEmpty = false}. + */ public static Float askFloat(String prompt, Float defaultValue) { return askFloat(prompt, defaultValue, null, null, false); } + /** + * Convenience method for {@link #askFloat(String, Float, Float, Float, boolean)} with no defaultValue or min/max range and {@code allowEmpty = false}. + */ public static Float askFloat(String prompt) { return askFloat(prompt, null, null, null, false); } /** * Asks the user for a long value using the specified prompt on the command line. - * The user is prompted until a valid long (within optional ranges) is provided. + *

+ * The user is prompted until a valid long (optionally enforced via {@code min}/{@code max}) + * is provided. If {@code defaultValue} is specified and the user presses Enter, that default is + * returned. If {@code defaultValue} is {@code null} and the user presses Enter, the behavior + * depends on {@code allowEmpty}: + *

* * - * @param prompt The prompt displayed to the user - * @param defaultValue The default long if user simply presses Enter (may be null) - * @param min The minimum acceptable value (inclusive), or null if no lower bound - * @param max The maximum acceptable value (inclusive), or null if no upper bound - * @return A Long value that the user entered, or the defaultValue, or null if no defaultValue was given + * @param prompt the prompt displayed to the user + * @param defaultValue the default long if user simply presses Enter (may be {@code null}) + * @param min the minimum acceptable value (inclusive), or {@code null} if no lower bound + * @param max the maximum acceptable value (inclusive), or {@code null} if no upper bound + * @param allowEmpty if {@code true}, empty input returns {@code null} when {@code defaultValue} is null + * @return a {@code Long} value entered by the user, or {@code defaultValue}, or {@code null} */ public static Long askLong(String prompt, Long defaultValue, Long min, Long max, boolean allowEmpty) { while (true) { - // If we have a defaultValue, display it in brackets; otherwise display no default String displayPrompt = prompt + (defaultValue != null ? " [" + defaultValue + "]" : "") + ": "; - // Read user input System.out.print(displayPrompt); - String input = new Scanner(System.in).nextLine().trim(); - - if (input.isEmpty()){ + String input = SCANNER.nextLine().trim(); - // If a defaultValue was supplied, return it + if (input.isEmpty()) { if (defaultValue != null) { return defaultValue; } - if (allowEmpty) { return null; } else { @@ -198,76 +231,70 @@ public class CLIHelper { } } - // Parse long value try { long parsedValue = Long.parseLong(input); - - // Check against min if specified if (min != null && parsedValue < min) { System.out.println("Value must be at least " + min + "."); continue; } - - // Check against max if specified if (max != null && parsedValue > max) { System.out.println("Value must be at most " + max + "."); continue; } - - // Parsed successfully within optional bounds return parsedValue; - } catch (NumberFormatException e) { System.out.println("Invalid number format. Try again."); } } } + /** + * Convenience method for {@link #askLong(String, Long, Long, Long, boolean)} with no min/max range and {@code allowEmpty = false}. + */ public static Long askLong(String prompt, Long defaultValue) { return askLong(prompt, defaultValue, null, null, false); } + /** + * Convenience method for {@link #askLong(String, Long, Long, Long, boolean)} with no defaultValue or min/max range and {@code allowEmpty = false}. + */ public static Long askLong(String prompt) { return askLong(prompt, null, null, null, false); } - /** * Asks the user for an integer value using the specified prompt on the command line. - * The user is prompted until a valid integer (within optional ranges) is provided. + *

+ * The user is prompted until a valid integer (optionally enforced via {@code min}/{@code max}) + * is provided. If {@code defaultValue} is specified and the user presses Enter, that default is + * returned. If {@code defaultValue} is {@code null} and the user presses Enter, the behavior + * depends on {@code allowEmpty}: + *

* * - * @param prompt The prompt displayed to the user. - * @param defaultValue The default long if user simply presses Enter (may be null). - * @param min The minimum acceptable value (inclusive), or null if no lower bound. - * @param max The maximum acceptable value (inclusive), or null if no upper bound. - * @param allowEmpty If true, empty input is acceptable. Otherwise keep asking user. - * @return An integer value that the user entered, or the defaultValue, or null if no defaultValue was given. + * @param prompt the prompt displayed to the user + * @param defaultValue the default integer if the user simply presses Enter (may be {@code null}) + * @param min the minimum acceptable value (inclusive), or {@code null} if no lower bound + * @param max the maximum acceptable value (inclusive), or {@code null} if no upper bound + * @param allowEmpty if {@code true}, empty input returns {@code null} when {@code defaultValue} is null + * @return an {@code Integer} value that the user entered, or the {@code defaultValue}, or {@code null} if allowed */ public static Integer askInteger(String prompt, Integer defaultValue, Integer min, Integer max, boolean allowEmpty) { while (true) { - // If we have a defaultValue, display it in brackets; otherwise display no default String displayPrompt = prompt + (defaultValue != null ? " [" + defaultValue + "]" : "") + ": "; - // Read user input System.out.print(displayPrompt); - String input = new Scanner(System.in).nextLine().trim(); - - // If user just pressed Enter: - if (input.isEmpty()){ + String input = SCANNER.nextLine().trim(); - // If a defaultValue was supplied, return it + if (input.isEmpty()) { if (defaultValue != null) { return defaultValue; } - if (allowEmpty) { return null; } else { @@ -276,65 +303,72 @@ public class CLIHelper { } } - // Parse long value try { int parsedValue = Integer.parseInt(input); - - // Check against min if specified if (min != null && parsedValue < min) { System.out.println("Value must be at least " + min + "."); continue; } - - // Check against max if specified if (max != null && parsedValue > max) { System.out.println("Value must be at most " + max + "."); continue; } - - // Parsed successfully within optional bounds return parsedValue; - } catch (NumberFormatException e) { System.out.println("Invalid number format. Try again."); } } } + /** + * Convenience method for {@link #askInteger(String, Integer, Integer, Integer, boolean)} with no min/max range and {@code allowEmpty = false}. + */ public static Integer askInteger (String prompt, Integer defaultValue) { return askInteger(prompt, defaultValue, null, null, false); } + /** + * Convenience method for {@link #askInteger(String, Integer, Integer, Integer, boolean)} with no defaultValue or min/max range and {@code allowEmpty = false}. + */ public static Integer askInteger (String prompt) { return askInteger(prompt, null, null, null, false); } + /** * Asks the user for a string value using the specified prompt on the command line. - * If the user presses ENTER without typing anything and {@code defaultValue} is non-null, - * that default value is returned. + *

+ * If the user presses ENTER without typing anything: + *

+ * + *

+ * Additionally, if {@code minLength} or {@code maxLength} are specified, the input + * must fall within those character bounds. + *

* * @param prompt the message to display to the user - * @param defaultValue the value to return if the user provides no input - * @return the value typed by the user, or {@code defaultValue} if empty input + * @param defaultValue the value to return if the user provides no input (may be {@code null}) + * @param minLength the minimum number of characters required, or {@code null} if no lower bound + * @param maxLength the maximum number of characters allowed, or {@code null} if no upper bound + * @param allowEmpty if {@code true}, empty input returns {@code null} when {@code defaultValue} is null + * @return the value typed by the user, {@code defaultValue}, or {@code null} as described above */ public static String askString(String prompt, String defaultValue, Integer minLength, Integer maxLength, boolean allowEmpty) { while (true) { - // If we have a defaultValue, display it in brackets; otherwise display no default String displayPrompt = prompt + (defaultValue != null ? " [" + defaultValue + "]" : "") + ": "; - // Read user input System.out.print(displayPrompt); - String input = new Scanner(System.in).nextLine().trim(); + String input = SCANNER.nextLine().trim(); - if (input.isEmpty()){ - - // If a defaultValue was supplied, return it + if (input.isEmpty()) { if (defaultValue != null) { return defaultValue; } - if (allowEmpty) { return null; } else { @@ -347,7 +381,6 @@ public class CLIHelper { System.out.println("Input must be at least " + minLength + " characters long."); continue; } - if (maxLength != null && input.length() > maxLength) { System.out.println("Input must be at most " + maxLength + " characters long."); continue; @@ -357,12 +390,17 @@ public class CLIHelper { } } + /** + * Convenience method for {@link #askString(String, String, Integer, Integer, boolean)} with no min/max length and {@code allowEmpty = false}. + */ public static String askString(String prompt, String defaultValue) { return askString(prompt, defaultValue, null, null, false); } + /** + * Convenience method for {@link #askString(String, String, Integer, Integer, boolean)} with no defaultValue or min/max length and {@code allowEmpty = false}. + */ public static String askString(String prompt) { return askString(prompt, null, null, null, false); } - } -- 2.20.1