package MusicLandscape.util;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.function.Function;
import java.util.function.Predicate;package MusicLandscape.util;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.function.Function;
import java.util.function.Predicate;This is the same as in ES06, with a little refactoring sprinkled on top.
skippable is actually used now.Scanner object in use is now checked with the getter every time
we do scan(), so the initialisation time for the default scanner
which uses System.in is later. I think there was some buggy behaviour
in the tests. It is not the best design to create the scanner
ourselves here, I should have made the code that uses this class
pass in a Scanner object./**
* Generic console function for getting a new value for an entity field from the user.
*
* <p>
* Example:
*
* <pre>
* Integer num = new ConsoleScanner(Integer::parseInt, (Integer i) -> i > 0, null)
* .scan("Give me a number")
* </pre>
*
* Produces this output:
*
* <pre>
* Give me a number: <user inputs 123 and presses Enter>
* </pre>
*
* Leading to the variable <kbd>num</kbd> having the value <kbd>123</kbd>.
*
* @param <T> The type of the value being read
* @author Jonas Altrock (ew20b126@technikum-wien.at)
* @version 1
* @since ExerciseSheet04
*/
public class ConsoleScanner<T> {
/**
* constant to allow readable indication of a skippable input
*/
public static final boolean SKIPPABLE = true;
/**
* constant to allow readable indication of a non-skippable input
*/
public static final boolean NOT_SKIPPABLE = false;
/**
* The default input scanner. Uses System.in if not set from outside.
*/
public static Scanner defaultScanner;
/**
* Whether the input can be skipped (by pressing Enter for example).
*/
public boolean skippable = SKIPPABLE;
/**
* The input to value transformer function.
*/
public Function<String, T> transformer;
/**
* The input value validator function.
*/
public Predicate<T> validator;
/**
* The scanner object in use.
*/
public Scanner scanner;
/**
* A scanner for reading non-empty strings.
*/
public static final ConsoleScanner<String> nonEmptyString = new ConsoleScanner<>((String s) -> s, (String s) -> !s.isBlank(), null);
/**
* A scanner for reading positive integers.
*/
public static final ConsoleScanner<Integer> positiveInteger = new ConsoleScanner<>(Integer::parseInt, (Integer i) -> i >= 0, null);
/**
* Create a scanner for a single value. Skippable by default.
*
* @param transformer a function that transforms the input string to a value
* @param validator a function that validates the given value
* @param scanner optional Scanner to use, pass null to use the default scanner (System.in)
*/
public ConsoleScanner(
Function<String, T> transformer,
Predicate<T> validator,
Scanner scanner
) {
this.transformer = transformer;
this.validator = validator;
if (scanner != null) {
this.scanner = scanner;
}
}
/**
* Create a scanner for a single value.
*
* @param transformer a function that transforms the input string to a value
* @param validator a function that validates the given value
* @param scanner optional Scanner to use, pass null to use the default scanner (System.in)
* @param skippable whether the user is allowed to skip entry by just pressing Enter
*/
public ConsoleScanner(
Function<String, T> transformer,
Predicate<T> validator,
Scanner scanner,
boolean skippable
) {
this(transformer, validator, scanner);
this.skippable = skippable;
}
/**
* Duplicate this object and use a different Scanner.
*
* @param s the Scanner to use
* @return a new ConsoleScanner using the provided Scanner
*/
public ConsoleScanner<T> withScanner(Scanner s) {
return new ConsoleScanner<>(transformer, validator, s, skippable);
}
/**
* Duplicate this object and make it unskippable.
*
* @return a new ConsoleScanner that is not skippable.
*/
public ConsoleScanner<T> unskippable() {
return new ConsoleScanner<>(transformer, validator, scanner, NOT_SKIPPABLE);
}
/**
* Get the default scanner object. System.in by if not set otherwise.
*
* @return the default scanner
*/
public Scanner getDefaultScanner() {
if (defaultScanner != null) {
return defaultScanner;
}
return new Scanner(System.in);
}
/**
* Get Scanner in use
* @return Scanner
*/
public Scanner getScanner() {
if (scanner != null) {
return scanner;
}
return getDefaultScanner();
}
/**
* Prompt the user for an input value.
*
* @param prompt string to print before prompt input
* @return a value or null, if no value was given
*/
public T scan(String prompt) {
T value;
do {
System.out.print(prompt + ": ");
try {
String in = getScanner().nextLine();
if (skippable && in.isEmpty()) {
return null;
}
value = transformer.apply(in);
} catch (NoSuchElementException e) {
value = null;
if (skippable) {
return null;
}
} catch (Exception e) {
value = null;
}
if (value == null) {
System.out.println("Invalid input, try again?");
continue;
}
if (!validator.test(value)) {
System.out.println("Value not allowed, try again?");
value = null;
}
} while (value == null);
return value;
}
}