diff --git a/src/main/java/ic/doc/ReversePolishStack.java b/src/main/java/ic/doc/ReversePolishStack.java index 058c1aa..0a2f164 100644 --- a/src/main/java/ic/doc/ReversePolishStack.java +++ b/src/main/java/ic/doc/ReversePolishStack.java @@ -1,10 +1,13 @@ package ic.doc; +import java.util.LinkedList; import java.util.Stack; +import java.util.List; import java.util.stream.Collectors; public class ReversePolishStack { private final Stack stack = new Stack<>(); + private final List observers = new LinkedList<>(); public int getSize() { return stack.size(); @@ -12,6 +15,9 @@ public class ReversePolishStack { public void push(IntOperator intOperator) { stack.push(intOperator); + + // Notify observers of push, giving them the updated stack + observers.forEach(observer -> observer.actionOnUpdate(toString())); } private int evaluateNext() { @@ -36,6 +42,10 @@ public class ReversePolishStack { throw new ArithmeticException("Invalid notation, too many operators"); } stack.push(new IntOperator(Integer.toString(result), 0, args -> result)); + + // Notify observers of evaluation, giving them the result + observers.forEach(observer -> observer.actionOnUpdate(toString())); + return result; } @@ -43,4 +53,18 @@ public class ReversePolishStack { public String toString() { return stack.stream().map(IntOperator::toString).collect(Collectors.joining(" ")); } + + public void addObserver(Observer observer) { + observers.add(observer); + } + + public void removeObserver(Observer observer) { + observers.remove(observer); + } + + // Defines an observer to a reverse polish stack + public interface Observer { + // Called whenever the reverse polish stack being observed is modified. + void actionOnUpdate(String result); + } } diff --git a/src/main/java/ic/doc/RpnCalculator.java b/src/main/java/ic/doc/RpnCalculator.java index 0ff0187..048e230 100644 --- a/src/main/java/ic/doc/RpnCalculator.java +++ b/src/main/java/ic/doc/RpnCalculator.java @@ -1,12 +1,44 @@ package ic.doc; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import static ic.doc.RpnCalculatorGuiBuilder.rpnCalculatorGui; + public class RpnCalculator { private static final int DEFAULT_WIDTH = 400; private static final int DEFAULT_HEIGHT = 400; - public static void main(String[] args) { - RpnCalculatorGui calculatorGui = RpnCalculatorGui.withDimensions(DEFAULT_WIDTH, DEFAULT_HEIGHT); + // Define the range of numbers that can be input to the calculator + private static final int MIN_INPUT_NUMBER = 0; + private static final int MAX_INPUT_NUMBER = 9; + private static final List OPERATORS = List.of( + new IntOperator("+", 2, args -> args.get(0) + args.get(1)), + new IntOperator("-", 2, args -> args.get(0) - args.get(1)), + new IntOperator("*", 2, args -> args.get(0) * args.get(1)), + new IntOperator("/", 2, args -> args.get(0) / args.get(1)) + ); + + public static void main(String[] args) { + // Add the numbers 0-9 as operators, then add the standard operators + List operators = new ArrayList<>(); + IntStream.rangeClosed(MIN_INPUT_NUMBER, MAX_INPUT_NUMBER).forEach(i -> { + operators.add(new IntOperator(Integer.toString(i), 0, arguments -> i)); + }); + operators.addAll(OPERATORS); + + ReversePolishStack rps = new ReversePolishStack(); + RpnCalculatorGui calculatorGui = rpnCalculatorGui() + .withDefaultWidth(DEFAULT_WIDTH) + .withDefaultHeight(DEFAULT_HEIGHT) + .withOperators(operators) + .withPushOperator(rps::push) + .withEvaluateAction(rps::evaluate) + .build(); + + rps.addObserver(calculatorGui); calculatorGui.display(); } } diff --git a/src/main/java/ic/doc/RpnCalculatorGui.java b/src/main/java/ic/doc/RpnCalculatorGui.java index f13e8d2..ad6a4bd 100644 --- a/src/main/java/ic/doc/RpnCalculatorGui.java +++ b/src/main/java/ic/doc/RpnCalculatorGui.java @@ -4,33 +4,29 @@ import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.JButton; -import java.util.Map; -import java.util.stream.IntStream; +import java.util.List; +import java.util.function.Consumer; -public class RpnCalculatorGui { +public class RpnCalculatorGui implements ReversePolishStack.Observer { + // Constructor fields private final int defaultWidth; private final int defaultHeight; + private final List operators; + private final Consumer pushOperator; + private final Runnable evaluateAction; + + // UI elements + JTextField resultField; private static final int RESULT_FIELD_WIDTH = 10; - // Define the range of numbers that can be input to the calculator - private static final int MIN_INPUT_NUMBER = 0; - private static final int MAX_INPUT_NUMBER = 9; - - private static final Map operators = Map.of( - "+", new Object(), - "-", new Object(), - "*", new Object(), - "/", new Object() - ); - - public static RpnCalculatorGui withDimensions(int defaultWidth, int defaultHeight) { - return new RpnCalculatorGui(defaultWidth, defaultHeight); - } - - private RpnCalculatorGui(int defaultWidth, int defaultHeight) { + public RpnCalculatorGui(int defaultWidth, int defaultHeight, List operators, + Consumer pushOperator, Runnable evaluateAction) { this.defaultWidth = defaultWidth; this.defaultHeight = defaultHeight; + this.operators = operators; + this.pushOperator = pushOperator; + this.evaluateAction = evaluateAction; } public void display() { @@ -39,25 +35,28 @@ public class RpnCalculatorGui { JPanel panel = new JPanel(); - // Add buttons for each number input - IntStream.rangeClosed(MIN_INPUT_NUMBER, MAX_INPUT_NUMBER).forEach(n -> { - JButton button = new JButton(Integer.toString(n)); - panel.add(button); - }); - // Add buttons for each operator - operators.forEach((key, value) -> { - JButton button = new JButton(key); + operators.forEach(operator -> { + JButton button = new JButton(operator.toString()); + button.addActionListener(e -> pushOperator.accept(operator)); panel.add(button); }); - JTextField resultField = new JTextField(RESULT_FIELD_WIDTH); + resultField = new JTextField(RESULT_FIELD_WIDTH); + resultField.setEditable(false); panel.add(resultField); JButton evaluateButton = new JButton("⏎"); + evaluateButton.addActionListener(e -> evaluateAction.run()); panel.add(evaluateButton); frame.add(panel); frame.setVisible(true); } + + // When the stack is evaluated, update the result text box + @Override + public void actionOnUpdate(String result) { + resultField.setText(result); + } } diff --git a/src/main/java/ic/doc/RpnCalculatorGuiBuilder.java b/src/main/java/ic/doc/RpnCalculatorGuiBuilder.java new file mode 100644 index 0000000..cab5fbb --- /dev/null +++ b/src/main/java/ic/doc/RpnCalculatorGuiBuilder.java @@ -0,0 +1,48 @@ +package ic.doc; + +import java.util.List; +import java.util.function.Consumer; + +public class RpnCalculatorGuiBuilder { + private int defaultWidth; + private int defaultHeight; + private List operators; + private Consumer pushOperator; + private Runnable evaluateAction; + + private RpnCalculatorGuiBuilder() {} + + public static RpnCalculatorGuiBuilder rpnCalculatorGui() { + return new RpnCalculatorGuiBuilder(); + } + + public RpnCalculatorGuiBuilder withDefaultWidth(int defaultWidth) { + this.defaultWidth = defaultWidth; + return this; + } + + public RpnCalculatorGuiBuilder withDefaultHeight(int defaultHeight) { + this.defaultHeight = defaultHeight; + return this; + } + + public RpnCalculatorGuiBuilder withOperators(List operators) { + this.operators = operators; + return this; + } + + public RpnCalculatorGuiBuilder withPushOperator(Consumer operatorConsumer) { + this.pushOperator = operatorConsumer; + return this; + } + + public RpnCalculatorGuiBuilder withEvaluateAction(Runnable evaluateAction) { + this.evaluateAction = evaluateAction; + return this; + } + + public RpnCalculatorGui build() { + return new RpnCalculatorGui(defaultWidth, defaultHeight, operators, pushOperator, + evaluateAction); + } +}