Exception Chaining (Cause)
Problem
Each line should look like key=value, where the value is a whole number (digits only). Parse the value. If the value is not a number, the low-level parse error must be wrapped in a ConfigError that keeps the original error as its cause. If the line has no equals sign, that is also a ConfigError whose cause says no equals sign. Print ok key=number for good lines. For bad lines print config error: <message> (cause: <cause message>), where the messages are: bad line / invalid <key> and the causes are: no equals sign / not a number.
Constraints
- 1 <= lines <= 10^4
Examples
Input
port=8080 timeout=abc
Output
ok port=8080 config error: invalid timeout (cause: not a number)
Solutions
Chaining keeps both pieces of information: a clear high-level message for the caller and the original low-level error for debugging. Never throw away the cause when you wrap an exception.
/*
* Problem : Process lines.
* Input : Several lines.
* Output : One result per line.
* Approach: Wrap the low-level error.
* Time : O(n)
* Space : O(n)
* Java : 8 or newer.
*/
import java.util.Scanner;
public class Main {
// Step 1: ConfigError wraps a lower-level error. The second constructor argument is the cause.
static class ConfigError extends Exception {
ConfigError(String message, Throwable cause) {
super(message, cause);
}
}
// Step 2: Parse one line. Any low-level problem is wrapped, so the caller sees one kind of error.
static String parse(String line) throws ConfigError {
int equals = line.indexOf('=');
if (equals < 0) {
throw new ConfigError("bad line", new IllegalArgumentException("no equals sign"));
}
String key = line.substring(0, equals);
String value = line.substring(equals + 1);
try {
if (value.isEmpty() || !value.chars().allMatch(Character::isDigit)) {
throw new NumberFormatException("not a number");
}
return "ok " + key + "=" + Long.parseLong(value);
} catch (NumberFormatException error) {
throw new ConfigError("invalid " + key, error);
}
}
// This method solves the task. It receives the input and returns the answer.
static String[] solve(String[] lines) {
String[] output = new String[lines.length];
for (int i = 0; i < lines.length; i++) {
// Step 3: getCause() gives back the original error that we wrapped.
try {
output[i] = parse(lines[i]);
} catch (ConfigError error) {
output[i] = "config error: " + error.getMessage() + " (cause: " + error.getCause().getMessage() + ")";
}
}
return output;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
java.util.List<String> all = new java.util.ArrayList<>();
while (scanner.hasNextLine()) {
all.add(scanner.nextLine());
}
String[] lines = all.toArray(new String[0]);
System.out.println(String.join("\n", solve(lines)));
}
}
Hints (1)
- What is lost when you wrap an exception without the cause?