This is a simple calculator where the user can type out the calculation and hit enter, and the calculator would determine if it is a valid calculation or not by attempting to carry out the calculation. If it fails, then an error message is printed to the screen. If it is a valid calculation, the calculation is carried out.
- Is the code well written?
- Is it easily extendable i.e. implementing a button that switches between normal operator panel scientific one?
- Are there any problems with coupling and cohesion?
- What could be done to make the code more robust?
Calculator
engine:
public class Calculator {
@SuppressWarnings("unused")
private CalculatorGui gui;
private static final int BRACKETS = 5;
@SuppressWarnings("unused")
private static final int ORDERS = 4; //for future expansion;
private static final int DIVISION = 3;
private static final int MULTIPLICATION = 2;
private static final int ADDITION = 1;
private static final int SUBTRACTION = 0;
public Calculator() {
this.gui = new CalculatorGui(this);
}
public String getAnswer(String text){
try {
if(isSingleCalc(text)) {
return singleCalc(getFirstNum(text), getOperator(text), getSecondNum(text));
}else {
String[] nextAndIndexs = getNext(text);
nextAndIndexs[0] = getAnswer(nextAndIndexs[0]);
if(nextAndIndexs[0].equals("Invalid Calc")) {
return "Invalid Calc";
}
String step = insert(text,nextAndIndexs);
return getAnswer(step);
}
} catch (Exception e) {
return "Invalid Calc";
}
}
private boolean isSingleCalc(String text) {
boolean singleCalc = true;
try {
singleCalc(getFirstNum(text), getOperator(text), getSecondNum(text));
} catch(Exception e) {
singleCalc = false;
}
return singleCalc;
}
private String singleCalc(String firstNum, String operator, String secondNum) throws Exception {
float ans = 0f;
switch(operator) {
case "+": ans = Float.parseFloat(firstNum) + Float.parseFloat(secondNum);
break;
case "-": ans = Float.parseFloat(firstNum) - Float.parseFloat(secondNum);
break;
case "*": ans = Float.parseFloat(firstNum) * Float.parseFloat(secondNum);
break;
case "/": ans = Float.parseFloat(firstNum) / Float.parseFloat(secondNum);
break;
default: throw new Exception();
}
return "" + ans;
}
private String[] getNext(String text) {
String currentHighestOper ="";
int beginingIndex,endIndex, index;
beginingIndex = endIndex = index = 0;
for(int i = 0; i < text.length(); i++) {
if(isHigherPrecedence("" + text.charAt(i), currentHighestOper)) {
currentHighestOper = "" + text.charAt(i);
index = i;
}
}
if(text.charAt(index) != '(') {
beginingIndex = getBeginingIndex(index, text);
endIndex = getEndIndex(index, text);
} else {
beginingIndex = index + 1;
endIndex = getEndBracketIndex(index, text);
}
String[] next = {text.substring(beginingIndex, endIndex),"" + beginingIndex,"" + endIndex, "" + text.charAt(index)};
return next;
}
private int getBeginingIndex(int i, String text) {
for(int j = i-1; j >= 0; j--) {
if (isOperator("" +text.charAt(j)) && !isNegation(text,j)) {
return j + 1;
}
}
return 0;
}
private int getEndIndex(int i, String text) {
for(int j = i + 1; j < text.length(); j++) {
if (isOperator("" +text.charAt(j)) && !isNegation(text, j)) {
return j;
}
}
return text.length();
}
private int getEndBracketIndex(int i, String text) {
for(int j = i; j < text.length(); j++) {
if(text.charAt(j) == ')') {
return j;
}
}
return text.length() - 1;
}
private boolean isOperator(String text) {
if(text.charAt(0) == '+' ||
text.charAt(0) == '-' ||
text.charAt(0) == '*' ||
text.charAt(0) == '/') {
return true;
} else {
return false;
}
}
private boolean isNegation(String text, int j) {
if((j==0 || isOperator("" +text.charAt(j-1)) || isOperator("" +text.charAt(j-1))) && text.charAt(j) == '-') {
return true;
}else {
return false;
}
}
private boolean isHigherPrecedence(String oper, String highest) {
int operValue = getPrecedenceValue(oper);
int highestValue = getPrecedenceValue(highest);
return (operValue >= highestValue)? true: false;
}
private int getPrecedenceValue(String oper) {
switch(oper) {
case "(": return BRACKETS;
case "/": return DIVISION;
case "*": return MULTIPLICATION;
case "+": return ADDITION;
case "-": return SUBTRACTION;
default: return -1;
}
}
private String insert(String mainAnswer,String[] nextAndIndexs) {
StringBuilder sb = new StringBuilder(mainAnswer);
if(!nextAndIndexs[3].equals("(")) {
return sb.replace(Integer.parseInt(nextAndIndexs[1]),Integer.parseInt(nextAndIndexs[2]), nextAndIndexs[0]).toString();
}else {
return sb.replace(Integer.parseInt(nextAndIndexs[1]) - 1,Integer.parseInt(nextAndIndexs[2]) + 1, nextAndIndexs[0]).toString();
}
}
private String getFirstNum(String text) {
String firstNum = null;
for(int i =0; i <text.length(); i++) {
if(isOperator("" + text.charAt(i)) && !isNegation(text,i)) {
firstNum = text.substring(0, i);
}
}
return firstNum;
}
private String getOperator(String text) {
String operator = null;
for(int i =0; i <text.length(); i++) {
if(isOperator("" + text.charAt(i))&& !isNegation(text,i)) {
operator = text.substring(i, i+1);
}
}
return operator;
}
private String getSecondNum(String text) {
String secondNum = null;
for(int i =0; i <text.length(); i++) {
if(isOperator("" + text.charAt(i))&& !isNegation(text,i)) {
secondNum = text.substring(i + 1, text.length());
}
}
return secondNum;
}
public static void main(String[] args) {
@SuppressWarnings("unused")
Calculator calc = new Calculator();
}
}
GUI:
public interface CalculatorGUIInterface {
public void writeToScreen(String text);
public void clearScreen();
public String getScreenText();
public String getAnswer();
}
public interface CalculatorInterface {
public void writeToScreen(String text);
public void clearScreen();
public String getScreenText();
public String getAnswer();
}
public interface CalculatorNumPanelInterface {
public void writeToScreen(String text);
public void clearScreen();
public String getScreenText();
}
public interface CalculatorOperPanelInterface {
public void writeToScreen(String text);
public void clearScreen();
public String getScreenText();
public String getAnswer();
}
import java.awt.Color;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;
@SuppressWarnings("serial")
public class CalculatorGui extends JFrame implements CalculatorGUIInterface {
private class CalculatorPanel extends JPanel implements CalculatorInterface {
private class NumberPanel extends JPanel implements CalculatorNumPanelInterface {
private static final int NUMTOTAL = 10;
private CalculatorPanel calcPanel;
private JButton[] numButtons;
public NumberPanel(CalculatorPanel calcPanel) {
this.calcPanel = calcPanel;
buildLayout();
addButtons();
}
private void buildLayout() {
this.setBorder(new EmptyBorder(0,0,0,5));
this.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
GridLayout layout = new GridLayout(4,3);
layout.setHgap(1);
layout.setVgap(1);
this.setLayout(new GridLayout(4,3));
}
private void addButtons() {
numButtons = new JButton[NUMTOTAL];
for(int i = numButtons.length -1; i >= 0 ; i--) {
numButtons[i] = new JButton("" + i);
numButtons[i].setPreferredSize(new Dimension(60,40));
numButtons[i].setFont(new Font("Sans serif", Font.PLAIN, 18));
numButtons[i].addActionListener(
new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String text = ((JButton)e.getSource()).getText().trim();
if(getScreenText().equals("Invalid Calc")) {
clearScreen();
writeToScreen(text);
}else {
writeToScreen(text);
}
}
});
this.add(numButtons[i]);
}
}
@Override
public void writeToScreen(String text) {
calcPanel.writeToScreen(text);
}
@Override
public void clearScreen() {
calcPanel.clearScreen();
}
@Override
public String getScreenText() {
return calcPanel.getScreenText();
}
}
private class OperPanel extends JPanel implements CalculatorOperPanelInterface {
private static final int ADD = 0;
private static final int SUB = 1;
private static final int MULT = 2;
private static final int DIV = 3;
private static final int OPENB = 4;
private static final int CLOSEB = 5;
private static final int CLEAR = 6;
private static final int EQL = 7;
private static final int OPERTOTAL = 8;
private CalculatorPanel calcPanel;
private JButton[] operButtons;
public OperPanel(CalculatorPanel calcPanel) {
this.calcPanel = calcPanel;
buildLayout();
addButtons();
}
private void buildLayout() {
GridLayout layout = new GridLayout(4,1);
layout.setHgap(1);
layout.setVgap(1);
this.setLayout(new GridLayout(4,1));
}
private void addButtons() {
operButtons = new JButton[OPERTOTAL];
operButtons[ADD] = makeButton(ADD, "+");
operButtons[SUB] = makeButton(SUB, "-");
operButtons[MULT] = makeButton(MULT, "*");
operButtons[DIV] = makeButton(DIV, "/");
operButtons[CLEAR] = makeButton(CLEAR, "CL");
operButtons[EQL] = makeButton(EQL, "=");
operButtons[OPENB] = makeButton(OPENB, "(");
operButtons[CLOSEB] = makeButton(CLOSEB, ")");
for(JButton button: operButtons) {
this.add(button);
}
}
private JButton makeButton(int index, String label) {
operButtons[index] = new JButton(label);
operButtons[index].addActionListener(
new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String screenText = "";
String text = ((JButton)e.getSource()).getText();
if(text.equals("=")) {
screenText = getAnswer();
clearScreen();
writeToScreen(screenText);
}else if(text.equals("CL")) {
clearScreen();
}else {
writeToScreen(text);
}
}
});
return operButtons[index];
}
@Override
public String getScreenText() {
return calcPanel.getScreenText();
}
@Override
public void clearScreen() {
calcPanel.clearScreen();
}
@Override
public void writeToScreen(String text) {
calcPanel.writeToScreen(text);
}
@Override
public String getAnswer() {
return calcPanel.getAnswer();
}
}
private NumberPanel numPanel;
private OperPanel operPanel;
private CalculatorGui gui;
public CalculatorPanel(CalculatorGui gui) {
this.gui = gui;
buildNumPanel();
buildOperPanel();
buildCalcPanel();
}
private void buildNumPanel() {
this.numPanel = new NumberPanel(this);
}
private void buildOperPanel() {
this.operPanel = new OperPanel(this);
}
private void buildCalcPanel() {
this.setBorder(new EmptyBorder(10,0,0,0));
this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
this.add(numPanel);
this.add(operPanel);
}
@Override
public void writeToScreen(String text) {
gui.writeToScreen(text);
}
@Override
public String getScreenText() {
return gui.getScreenText();
}
@Override
public void clearScreen() {
gui.clearScreen();
}
@Override
public String getAnswer() {
return gui.getAnswer();
}
}
private Calculator calc;
private JPanel mainPanel;// used to add padding around Calculator Panel
private JTextField calcScreen;
private CalculatorPanel calcPanel;
public CalculatorGui(Calculator calc) {
this.calc = calc;
buildScreen();
buildCalcPanel();
buildMainPanel();
buildCalculator();
}
private void buildScreen() {
this.calcScreen = new JTextField();
this.calcScreen.setEditable(false);
this.calcScreen.setBackground(Color.WHITE);
this.calcScreen.setPreferredSize(new Dimension(150,50));
this.calcScreen.setHorizontalAlignment(JTextField.CENTER);
this.calcScreen.setFont(new Font("Sans serif", Font.PLAIN, 30));
}
private void buildCalcPanel() {
this.calcPanel = new CalculatorPanel(this);
}
private void buildMainPanel() {
this.mainPanel = new JPanel();
this.mainPanel.setBorder(new EmptyBorder(10,10,10,10));
this.mainPanel.setLayout(new BoxLayout(this.mainPanel, BoxLayout.Y_AXIS));
this.mainPanel.add(calcScreen);
this.mainPanel.add(calcPanel);
}
private void buildCalculator() {
this.add(mainPanel);
this.setTitle("Calculator");
this.pack();
this.setResizable(false);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
@Override
public String getAnswer() {
return calc.getAnswer(calcScreen.getText());
}
@Override
public void writeToScreen(String text) {
calcScreen.setText(getScreenText() + text);
}
@Override
public void clearScreen() {
calcScreen.setText("");
}
@Override
public String getScreenText() {
return calcScreen.getText();
}
}
isValid
as all does is finding out ifgetAnswer
succeeds. This is a duplicate work, letgetAnswer
throw on invalid input (or returnnull
or whatever). For now, forget parentheses and add some trivial evaluation code, even ignoring operator precedence should do. Find an operator, find the operands, execute. Repeat until done. Then you code can be reviewed and we can give you also tips for extensions (as a side result, don't ask for them). – maaartinus Jul 12 at 14:44