Simple Usage of Sandbox - HolmesJJ/OOP-FP GitHub Wiki

qaTest.java

import java.util.ArrayList;
import java.util.List;

public class qaTest {

    public static void displayResults(List<Boolean> rList) {
        int marks = 0;
        for (boolean a : rList) {
            if (a) {
                marks++;
            }
        }
        System.out.printf("You got %d questions correct.%n", marks);
    }

    public static void main(String[] args) {
        List<Boolean> results = new ArrayList<>();
        List<QA> questions = new ArrayList<>();
        questions.add(new MCQ("What is 1+1?", 'A'));
        questions.add(new TFQ("The sky is blue (T/F)", 'T'));
        questions.add(new MCQ("Which animal is an elephant?", 'C'));
        questions.add(new TFQ("A square is a circle (T/F)", 'F'));
        for (QA q : questions) {
            Sandbox.make(q)
                    .map(QA::displayQuestion)
                    .map(QA::getAnswer)
                    .consume(results::add);
        }
        displayResults(results);
    }
}

QA.java

import java.util.Scanner;

abstract class QA {

    String question;
    char correctAnswer;

    public QA(String question, char ans) {
        this.question = question;
        this.correctAnswer = ans;
    }
    
    abstract boolean getAnswer();

    public QA displayQuestion() {
        System.out.println(this.question);
        return this;
    }
        
    public char getInput() {
        Scanner sc = new Scanner(System.in);
        System.out.print("=> ");
        char result = sc.next().charAt(0);
        System.out.println("");
        return result;
    }
}

MCQ.java

class MCQ extends QA {
    public MCQ(String question, char ans) {
        super(question, ans);
    }

    @Override
    boolean getAnswer() {
        char answer = this.getInput();
        if (answer < 'A' || answer > 'E') {
            throw new InvalidMCQException("Invalid MCQ answer");
        }
        return this.correctAnswer == answer;
    }
}

TFQ.java

class TFQ extends QA {
    public TFQ(String question, char ans) {
        super(question, ans);
    }

    @Override
    boolean getAnswer() {
        char answer = this.getInput();        
        if (answer != 'T' && answer != 'F') {
            throw new InvalidTFQException("Invalid TFQ answer");
        }
        return this.correctAnswer == answer;
    }
}

InvalidQuestionException.java

class InvalidQuestionException extends IllegalArgumentException {
    public InvalidQuestionException(String mesg) {
        super(mesg);
    }
}

InvalidMCQException.java

class InvalidMCQException extends InvalidQuestionException {
    public InvalidMCQException(String mesg) {
        super(mesg);
    }
}

InvalidTFQException.java

class InvalidTFQException extends InvalidQuestionException {
    public InvalidTFQException(String mesg) {
        super(mesg);
    }
}

Sandbox.java

/**
 * Class Sandbox is a Functor, and is an immutable class.
 * It's purpose is to separate exception handling from the main
 * sequence of mapping.
 */

import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Consumer;
import java.util.Objects;

public final class Sandbox<T> {

    private final T thing;
    private final Exception exception;

    private Sandbox(T thing, Exception ex) {
        this.thing = thing;
        this.exception = ex;
    }

    public T getThing() {
        return this.thing;
    }

    public static <T> Sandbox<T> make(T thing) {
        // The instantiated object cannot be null
        return new Sandbox<T>(Objects.requireNonNull(thing), null);
    }

    public static <T> Sandbox<T> makeEmpty() {
        return new Sandbox<T>(null, null);
    }

    public boolean isEmpty() {
        return this.thing == null;
    }

    public boolean hasNoException() {
        return this.exception == null;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof Sandbox) {
            Sandbox<?> other = (Sandbox<?>) obj;
            return this.thing.equals(other.thing) &&
                    this.exception.equals(other.exception);
        }
        return false;
    }

    public <U> Sandbox<U> map(Function<T, U> f) {
        return map(f, "");
    }

    // return new Sandbox Object
    public <U> Sandbox<U> map(Function<T, U> f, String errorMessage) {
        if (this.isEmpty()) {
            return new Sandbox<U>(null, this.exception);
        }
        try {
            U result = f.apply(this.thing);
            return new Sandbox<U>(result, null);
        }
        // If result throws exception, such as unknown command, too many arguments
        catch (Exception ex) {
            return new Sandbox<U>(null,
                                  new Exception(errorMessage, ex));
        }
    }

    /**
     * consume calls the Consumer argument with the thing.
     * Exceptions, if any, are handled here.
     */
    public void consume(Consumer<T> eat) {
        if (this.isEmpty() && !this.hasNoException())
            handleException();

        if (!this.isEmpty() && this.hasNoException())
            eat.accept(this.thing);
    }

    private void handleException() {
        System.err.printf(this.exception.getMessage());
        Throwable t = this.exception.getCause();
        if (t != null) {
            String msg = t.getMessage();
            if (msg != null)
                System.err.printf(": %s", msg);
        }
        System.err.println("");
    }

    public <U> Sandbox<U> flatMap(Function<T, Sandbox<U>> f) {
        return flatMap(f, "");
    }

    public <U> Sandbox<U> flatMap(Function<T, Sandbox<U>> f, String errorMessage) {
        if (this.isEmpty()) {
            return new Sandbox<U>(null, this.exception);
        }
        try {
            return f.apply(this.thing);
        }
        // If result throws exception
        catch (Exception ex) {
            return new Sandbox<U>(null,
                    new Exception(errorMessage, ex));
        }
    }

    public <U,R> Sandbox<R> combine(Sandbox<U> s,
                                    BiFunction<T,U,R> binOp) {
        return this.flatMap(t1 ->
                s.map(t2 -> binOp.apply(t1, t2)));
    }
}
⚠️ **GitHub.com Fallback** ⚠️