Exception Chaining (Cause)

hard Exception Handling

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

Best approachTime O(n)Space O(n)

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.

Same code works in Java810172125Basic features only, so nothing changes between versions.
/*
 * 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)
  1. What is lost when you wrap an exception without the cause?