6
\$\begingroup\$

Let me start off by saying that this project is a mess, and I know it. I believe it is a mess because I threw it together in a few days just to get it working. Unfortunately, my undergraduate education hasn't taught me design patterns (yet), so there is really no organized structure.

If I want to develop this into a more organized project, and perhaps a more general math library, with more functionality (integration, statistics, etc), what can I do to design it very well? Where do I put the logic, etc. I have been reading up on design patterns but I still feel that I need direction.

MathApp.java

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;

import javax.swing.*;

public class MathApp extends Application {

    private CalculusTool cT;
    private String expression;
    private String deriv;

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("MathApp");

        GridPane grid = new GridPane();
        grid.setAlignment(Pos.CENTER);
        grid.setHgap(10);
        grid.setVgap(10);
        grid.setPadding(new Insets(25, 25, 25, 25));

        Text scenetitle = new Text("Enter an expression");
        scenetitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 15));

        grid.add(scenetitle, 0, 0, 2, 1);

        TextField expBox = new TextField();
        grid.add(expBox, 1, 2);

        Text error = new Text();
        grid.add(error, 1, 6);
        error.setVisible(false);

        Button btn = new Button("Derive");
        HBox hbBtn = new HBox(10);
        hbBtn.setAlignment(Pos.BOTTOM_RIGHT);
        hbBtn.getChildren().add(btn);
        grid.add(hbBtn, 1, 4);

        final Text actiontarget = new Text();
        grid.add(actiontarget, 1, 6);

        btn.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent e) {

                //same expression - derive again when clicked
                if(expBox.hasProperties() && expBox.getText().equals(expression)) {
                    deriv = cT.readable(cT.derive());

                    actiontarget.setVisible(true);
                    actiontarget.setFill(Color.FIREBRICK);
                    actiontarget.setText(deriv);
                }
                else { // new expression, create new object
                    expression = expBox.getText();

                    if (!CalculusTool.isValidExpression(expression)) {
                        actiontarget.setVisible(false);
                        error.setText(CalculusTool.errorMessage);
                        error.setVisible(true);
                        expBox.clear();
                    } else {
                        error.setVisible(false);
                        cT = new CalculusTool(expression);
                        deriv = cT.readable(cT.derive());

                        actiontarget.setVisible(true);
                        actiontarget.setFill(Color.FIREBRICK);
                        actiontarget.setText(deriv);
                    }
                }
            }
        });

        Scene scene = new Scene(grid, 300, 275);
        primaryStage.setScene(scene);

        primaryStage.show();
    }


    public static void main(String[] args) {
        launch(args);
    }
}

ExpNode.java

abstract class ExpNode {

    abstract String getVal();
    abstract String getType();
    abstract ExpNode getLeftChild();
    abstract ExpNode getRightChild();
    abstract void setLeftChild(ExpNode l);
    abstract void setRightChild(ExpNode r);
    abstract ExpNode derivative();
}

class OpNode extends ExpNode
{
    String operator;
    ExpNode leftChild;
    ExpNode rightChild;

    OpNode mult1;
    OpNode mult2;
    OpNode mult3;
    OpNode div;
    OpNode plus;
    OpNode minus;
    OpNode exponent;
    NegateNode unary;

    OpNode(String op)
    {
        operator = op;
        leftChild = null;
        rightChild = null;
    }

    String getVal() { return operator; }
    String getType() { return "operator"; }
    ExpNode getLeftChild() { return leftChild; }
    ExpNode getRightChild() { return rightChild; }

    void setLeftChild(ExpNode lc) { leftChild = lc; }
    void setRightChild(ExpNode rc) { rightChild = rc; }

    ExpNode derivative() {

        switch(operator) {

            case "+": // h(x) = f(x)+g(x) then h′(x) = f′(x)+g′(x)
                plus = new OpNode("+");

                plus.setLeftChild(getLeftChild().derivative());
                plus.setRightChild(getRightChild().derivative());

                return plus;
            case "-": // h(x) = f(x)-g(x) then h′(x) = f′(x)-g′(x)
                minus = new OpNode("-");

                minus.setLeftChild(getLeftChild().derivative());
                minus.setRightChild(getRightChild().derivative());

                return minus;
            case "*": // h(x) = f(x)g(x) then h′(x) = f′(x)g(x) + f(x)g′(x)
                mult1 = new OpNode("*");
                mult2 = new OpNode("*");
                plus = new OpNode("+");

                mult1.setLeftChild(getLeftChild());
                mult1.setRightChild(getRightChild().derivative());
                mult2.setLeftChild(getRightChild());
                mult2.setRightChild(getLeftChild().derivative());
                plus.setLeftChild(mult1);
                plus.setRightChild(mult2);

                return plus;
            case "/": // h(x) = f(x)/g(x) then h′(x) = (f′(x)g(x) − f(x)g′(x))/( g(x)^2)
                mult1 = new OpNode("*");
                mult2 = new OpNode("*");
                mult3 = new OpNode("*");
                div = new OpNode("/");
                minus = new OpNode("-");

                mult1.setLeftChild(getRightChild());
                mult1.setRightChild(getLeftChild().derivative());
                mult2.setLeftChild(getLeftChild());
                mult2.setRightChild(getRightChild().derivative());
                minus.setLeftChild(mult1);
                minus.setRightChild(mult2);
                mult3.setLeftChild(getRightChild());
                mult3.setRightChild(getRightChild());
                div.setLeftChild(minus);
                div.setRightChild(mult3);

                return div;
            case "^":
                /*y = xsin x

                ln y = (sin x) ln x     ←     taking the natural log of both sides.

                y'/y = (cos x) ln x + (sin x)/x     ←     Using the product rule and implicit differentiation.

                y' = y((cos x) ln x + (sin x)/x)     ←     multiplying by y.

                y' = xsin x ((cos x) ln x + (sin x)/x) ← substitution.


                */
                if(getRightChild().getVal().equals("0")) {
                    return new ConstNode("1");
                }
                if(getLeftChild().getType().equals("function")) {

                    if(getRightChild().getType().equals("variable")) {

                    }
                    if(getRightChild().getType().equals("function")) {

                    }
                    if(getRightChild().getType().equals("constant")) {
                        int temp = Integer.parseInt(getRightChild().getVal());
                        temp--;
                        ConstNode decrement = new ConstNode(String.valueOf(temp));

                        mult1 = new OpNode("*");
                        mult2 = new OpNode("*");
                        exponent = new OpNode("^");
                        //getLeftChild() = func(x)
                        //getLeftChild().derivative() = dx*dfunc(x)
                        //getRightChild() = C
                        //decrement = C - 1
                        //getLeftChild().getRightChild() = x
                        // func(x)^C ==>>  C*dx*func(x)^(C-1)*dfunc(x)

                        exponent.setLeftChild(getLeftChild());
                        exponent.setRightChild(decrement);
                        mult1.setLeftChild(exponent);
                        mult1.setRightChild(getLeftChild().derivative());
                        mult2.setLeftChild(getRightChild());
                        mult2.setRightChild(mult1);

                        return mult2;
                    }
                }
                else if(getLeftChild().getType().equals("variable")) {
                    if(getRightChild().getType().equals("unaryMinus")) {
                        if(getRightChild().getRightChild().getType().equals("constant")) { // Ex: x^-3 ==> -3*(x^-4)
                            int temp = Integer.parseInt(getRightChild().getRightChild().getVal());
                            temp++;
                            ConstNode decrement = new ConstNode(String.valueOf(temp));

                            mult1 = new OpNode("*");
                            exponent = new OpNode("^");
                            unary.setRightChild(getRightChild().getRightChild());

                            exponent.setLeftChild(getLeftChild());
                            exponent.setRightChild(decrement);
                            mult1.setLeftChild(unary);
                            mult1.setRightChild(exponent);

                            return mult1;
                        }
                    }

                    // if not unaryMinus
                    if(getRightChild().getType().equals("variable")) {

                    }
                    if(getRightChild().getType().equals("function")) {

                    }
                    if(getRightChild().getType().equals("constant")) { // Ex: x^3 ==> 3*(x^2)
                        int temp = Integer.parseInt(getRightChild().getVal());
                        temp--;
                        ConstNode decrement = new ConstNode(String.valueOf(temp));

                        mult1 = new OpNode("*");
                        exponent = new OpNode("^");

                        exponent.setLeftChild(getLeftChild());
                        exponent.setRightChild(decrement);
                        mult1.setLeftChild(getRightChild());
                        mult1.setRightChild(exponent);

                        return mult1;
                    }
                }
                else if(getLeftChild().getType().equals("constant")) {
                    if(getRightChild().getType().equals("variable")) {

                    }
                    if(getRightChild().getType().equals("function")) {

                    }
                    if(getRightChild().getType().equals("constant")) {
                        return new ConstNode("0");
                    }
                }
                return new ConstNode("0");
        }

        return null;
    }
}
// only has right child
class FuncNode extends ExpNode
{
    String func;
    ExpNode leftChild;
    ExpNode rightChild;

    NegateNode unary;
    OpNode exponent;
    OpNode mult;
    OpNode mult2;
    OpNode div;
    FuncNode sin;
    FuncNode cos;
    FuncNode sec;
    FuncNode cot;
    FuncNode csc;
    FuncNode tan;
    FuncNode ln;
    ConstNode two;
    ConstNode ten;

    FuncNode(String f)
    {
        func = f;
        leftChild = null;
        rightChild = null;
    }

    String getVal() { return func; }
    String getType() { return "function"; }
    ExpNode getLeftChild() { return leftChild; }
    ExpNode getRightChild() { return rightChild; }

    void setLeftChild(ExpNode lc) { leftChild = lc; }
    void setRightChild(ExpNode rc) { rightChild = rc; }

    ExpNode derivative() {

        switch(func) {
            case "sin":
                mult = new OpNode("*");
                cos = new FuncNode("cos");

                cos.setRightChild(getRightChild());
                mult.setLeftChild(getRightChild().derivative());
                mult.setRightChild(cos);

                return mult;
            case "cos":
                unary = new NegateNode();
                mult = new OpNode("*");
                sin = new FuncNode("sin");

                sin.setRightChild(getRightChild());
                mult.setLeftChild(getRightChild().derivative());
                mult.setRightChild(sin);
                unary.setRightChild(mult);

                return unary;
            case "tan":
                sec = new FuncNode("sec");
                mult = new OpNode("*");
                exponent = new OpNode("^");
                two = new ConstNode("2");

                sec.setRightChild(getRightChild());
                exponent.setLeftChild(sec);
                exponent.setRightChild(two);
                mult.setLeftChild(getRightChild().derivative());
                mult.setRightChild(exponent);

                return mult;
            case "csc":
                unary = new NegateNode();
                mult = new OpNode("*");
                mult2 = new OpNode("*");
                cot = new FuncNode("cot");
                csc = new FuncNode("csc");

                cot.setRightChild(getRightChild());
                csc.setRightChild(getRightChild());
                mult.setLeftChild(cot);
                mult.setRightChild(csc);
                mult2.setLeftChild(getRightChild().derivative());
                mult2.setRightChild(mult);
                unary.setRightChild(mult2);

                return unary;
            case "sec":
                mult = new OpNode("*");
                mult2 = new OpNode("*");
                sec = new FuncNode("sec");
                tan = new FuncNode("tan");

                sec.setRightChild(getRightChild());
                tan.setRightChild(getRightChild());
                mult.setLeftChild(sec);
                mult.setRightChild(tan);
                mult2.setLeftChild(getRightChild().derivative());
                mult2.setRightChild(mult);

                return mult2;
            case "cot":
                unary = new NegateNode();
                exponent = new OpNode("^");
                mult = new OpNode("*");
                csc = new FuncNode("csc");
                two = new ConstNode("2");

                csc.setRightChild(getRightChild());
                exponent.setLeftChild(csc);
                exponent.setRightChild(two);
                mult.setLeftChild(getRightChild().derivative());
                mult.setRightChild(exponent);
                unary.setRightChild(mult);

                return unary;
            case "log":
                div = new OpNode("/");
                mult = new OpNode("*");
                ln = new FuncNode("ln");
                ten = new ConstNode("10");

                ln.setRightChild(ten);
                mult.setLeftChild(getRightChild());
                mult.setRightChild(ln);
                div.setLeftChild(getRightChild().derivative());
                div.setRightChild(mult);

                return div;
            case "ln":
                div = new OpNode("/");

                div.setLeftChild(getRightChild().derivative());
                div.setRightChild(getRightChild());

                return div;
            //case "arcsin":
            //case "arccos":
        }

        return null;
    }
}

class ConstNode extends ExpNode
{
    int num;
    String strNum;
    ExpNode leftChild;
    ExpNode rightChild;
    boolean string = false;

    ConstNode(int val)
    {
        num = val;
        strNum = "";
        leftChild = null;
        rightChild = null;
    }

    ConstNode(String val)
    {
        strNum = val;
        num = 0;
        leftChild = null;
        rightChild = null;
        string = true;
    }

    String getVal() { return strNum; }
    String getType() { return "constant"; }
    ExpNode getLeftChild() { return leftChild; }
    ExpNode getRightChild() { return rightChild; }

    void setLeftChild(ExpNode lc) { leftChild = lc; }
    void setRightChild(ExpNode rc) { rightChild = rc; }

    ExpNode derivative() { return new ConstNode("0"); }
}

class VarNode extends ExpNode
{
    String variable;
    ExpNode leftChild;
    ExpNode rightChild;

    VarNode(String v)
    {
        variable = v;
        leftChild = null;
        rightChild = null;
    }

    String getVal() { return variable; }
    String getType() { return "variable"; }
    ExpNode getLeftChild() { return leftChild; }
    ExpNode getRightChild() { return rightChild; }

    void setLeftChild(ExpNode lc) { leftChild = lc; }
    void setRightChild(ExpNode rc) { rightChild = rc; }

    ExpNode derivative() {
        return new ConstNode("1");
    }
}

// $ only has a right child
class NegateNode extends ExpNode
{
    String unaryMinus;
    ExpNode leftChild;
    ExpNode rightChild;
    NegateNode unary;

    NegateNode()
    {
        unaryMinus = "$";
        leftChild = null;
        rightChild = null;
    }
    NegateNode(String un)
    {
        unaryMinus = un;
        leftChild = null;
        rightChild = null;
    }

    String getVal() { return unaryMinus; }
    String getType() { return "unaryMinus"; }
    ExpNode getLeftChild() { return leftChild; }
    ExpNode getRightChild() { return rightChild; }

    void setLeftChild(ExpNode lc) { leftChild = lc; }
    void setRightChild(ExpNode rc) { rightChild = rc; }

    ExpNode derivative() {
        if(getRightChild().getType().equals("constant")) {
            return new ConstNode("0");
        }
        unary = new NegateNode("$");
        unary.setRightChild(getRightChild().derivative());

        return unary;
    }

}

CalculusTool.java

import java.util.ArrayList;
import java.util.Stack;

public class CalculusTool {

    public static String var = "";
    private String expression;
    private String derivative;
    private ArrayList<String> postFixTokens;
    private ExpNode root;
    private ExpNode derivativeRoot;
    private int COUNT = 10;
    static String errorMessage;

    public CalculusTool(String exp)
    {
        expression = formatString(exp);
        postFixTokens = tokenize(expression);
        root = constructTree(postFixTokens);
        derivativeRoot = root;
        derivative = expression;
    }

    public String getExpression()
    {
        return expression;
    }

    public void resetDerivative()
    {
        derivativeRoot = root;
        derivative = expression;
    }

    public static boolean isValidExpression(String exp)
    {
        exp = exp.replaceAll("\\s","");
        exp = exp.toLowerCase();

        if(exp.length() == 0) {
            errorMessage = "Nothing Entered";
            return false;
        }

        if(!exp.matches("[a-zA-Z0-9+*/^()-]+")) { // contains only operators, numbers, or letters
            errorMessage = "Syntax Error";
            return false;
        }

        if(exp.matches("[+*/^()-]+")) { // doesn't contain any operands
            errorMessage = "Syntax Error";
            return false;
        }

        String firstChar = exp.substring(0, 1);
        String lastChar = exp.substring(exp.length() - 1, exp.length());

        if(!firstChar.equals("-") && isOperator(firstChar) || firstChar.equals(")") || isOperator(lastChar) || lastChar.equals("(")) {
            errorMessage = "Syntax Error"; //starts with operator or close parenthesis, or ends with operator or open parenthesis
            return false;
        }


        for(int i = 0; i < exp.length(); i++) {

            String temp = "";

            while(i < exp.length() && exp.substring(i, i + 1).matches("[a-zA-Z]")) {
                temp += exp.substring(i, i + 1);
                i++;
            }

            if(temp.length() == 1) {
                //i--; // ?? i must be decremented from the above while loop in this if block so the program can check the last character in the string
                if(var.length() == 0)
                    var = temp;
                if(!temp.equals(var)) {
                    errorMessage = "Your expression cannot contain two variables";
                    return false;
                }
                else if(i < exp.length() && exp.substring(i, i + 1).matches("[0-9]+")) {
                    errorMessage = "Can't do this: " + temp + exp.substring(i, i + 1);
                    return false;
                }
            }
            else if(isFunction(temp)) {

                if(i < exp.length()) {
                    if(!exp.substring(i, i + 1).equals("(")) {
                        System.out.println("Syntax Error: " + temp + " needs a parenthesis after it");// no parenthesis after function (EX: sin5)
                        return false;
                    }
                }
                else {
                    System.out.println("Syntax Error: " + temp + " needs an input"); // nothing after function (EX: 5 + sin)
                    return false;
                }
            }
            else if(temp.length() != 0){
                System.out.println(temp + ": function not found");
                return false;
            }

            //i--; // ?? i must be decremented since it was left incremented in the above while loop
        }


        int cntOpenParen = 0;
        int cntCloseParen = 0;

        for(int i = 0; i < exp.length() - 1; i++) {

            String tmp1 = exp.substring(i, i + 1);
            String tmp2 = exp.substring(i + 1, i + 2);

            if(tmp1.equals("-")) {
                if(isOperator(tmp2) || tmp2.equals(")")) {
                    System.out.println("Syntax Error: " + tmp1 + tmp2);
                    return false;
                }
            }
            else if(tmp2.equals("-")) { // Also prevents next else if from rejecting an operator followed by a unary minus
                if(tmp1.equals("(")) {
                    System.out.println("Syntax Error: " + tmp1 + tmp2);
                    return false;
                }
            }
            else if((isOperator(tmp1) || tmp1.equals("(")) && (isOperator(tmp2) || tmp2.equals(")"))) {
                System.out.println("Syntax Error: " + tmp1 + tmp2); // two operands in a row (examples: ++, (+, ())
                return false;
            }
            else if(exp.substring(i, i + 1).equals("("))
                cntOpenParen++;
            else if(exp.substring(i, i + 1).equals(")"))
                cntCloseParen++;
        }

        if(cntOpenParen < cntCloseParen) { // found a ")" when the end of the expression was expected
            System.out.println("Syntax Error: found ')' but expected end of expression");
            return false;
        }

        return true;
    }

    public static boolean isOperator(String str)
    {
        if(str.matches("[+*/^-]"))
            return true;

        return false;
    }

    public boolean isOperand(String str)
    {
        if(str.matches("[0-9]+") || str.equals(var))
            return true;

        return false;
    }

    public static boolean isFunction(String str)
    {
        String[] funcs = {"sin", "cos", "tan", "csc", "sec", "cot", "log", "ln"};

        for(String temp: funcs)
            if(str.equals(temp))
                return true;

        return false;
    }

    // "simplifies" the tree by deleting, replacing and merging nodes
    public ExpNode simplify(ExpNode root)
    {
        if(root == null)
        {
            return root;
        }
        else if(root.getType().equals("unaryMinus") && root.getRightChild().getType().equals("unaryMinus")) {
            return root.getRightChild().getRightChild();
        }
        else if(root.getVal().equals("*")) {

            if(root.getLeftChild().getVal().equals("1")) {
                return root.getRightChild();
            }
            else if(root.getRightChild().getVal().equals("1")) {
                return root.getLeftChild();
            }
            else if(root.getLeftChild().getVal().equals("0")) {
                return root.getLeftChild();
            }
            else if(root.getRightChild().getVal().equals("0")) {
                return root.getRightChild();
            }
            else if(root.getLeftChild().getVal().equals("*")) {
                if(root.getRightChild().getType().equals("constant")) {
                    if(root.getLeftChild().getLeftChild().getType().equals("constant")) { // Ex: (5*x)*6 ==> 30*x
                        int num1 = Integer.parseInt(root.getRightChild().getVal());
                        int num2 = Integer.parseInt(root.getLeftChild().getLeftChild().getVal());
                        OpNode mult = new OpNode("*");
                        mult.setLeftChild(new ConstNode(String.valueOf(num1 * num2)));
                        mult.setRightChild(root.getLeftChild().getRightChild());

                        return mult;
                    }
                    if(root.getLeftChild().getRightChild().getType().equals("constant")) { // Ex: (x*5)*6 ==> 30*x
                        int num1 = Integer.parseInt(root.getRightChild().getVal());
                        int num2 = Integer.parseInt(root.getLeftChild().getRightChild().getVal());
                        OpNode mult = new OpNode("*");
                        mult.setLeftChild(new ConstNode(String.valueOf(num1 * num2)));
                        mult.setRightChild(root.getLeftChild().getLeftChild());

                        return mult;
                    }
                }
                if(root.getRightChild().getType().equals("variable")) {
                    if(root.getLeftChild().getLeftChild().getType().equals("variable")) {
                        OpNode exponent = new OpNode("^");
                        exponent.setLeftChild(root.getRightChild());
                        exponent.setRightChild(new ConstNode("2"));
                        if(root.getLeftChild().getRightChild().getType().equals("function")) { // Ex: (x*sin(x))*x ==> (x^2)*sin(x)
                            root.setRightChild(root.getLeftChild().getRightChild());
                            root.setLeftChild(exponent);
                        }
                        else {                                                                  // Ex: (x*5)*x ==> 5*x^2
                            root.setRightChild(exponent);
                            root.setLeftChild(root.getLeftChild().getRightChild());
                        }

                        return root;
                    }
                    if(root.getLeftChild().getRightChild().getType().equals("variable")) {
                        OpNode exponent = new OpNode("^");
                        exponent.setLeftChild(root.getRightChild());
                        exponent.setRightChild(new ConstNode("2"));
                        if(root.getLeftChild().getLeftChild().getType().equals("function")) { // Ex: (sin(x)*x)*x ==> (x^2)*sin(x)
                            root.setRightChild(root.getLeftChild().getLeftChild());
                            root.setLeftChild(exponent);
                        }
                        else {                                                                  // Ex: (5*x)*x ==> 5*x^2
                            root.setRightChild(exponent);
                            root.setLeftChild(root.getLeftChild().getLeftChild());
                        }

                        return root;
                    }
                }
            }
            else if(root.getRightChild().getVal().equals("*")) {
                if(root.getLeftChild().getType().equals("constant")) {
                    if(root.getRightChild().getLeftChild().getType().equals("constant")) { // Ex: 5*(6*x) ==> 30*x
                        int num1 = Integer.parseInt(root.getLeftChild().getVal());
                        int num2 = Integer.parseInt(root.getRightChild().getLeftChild().getVal());
                        OpNode mult = new OpNode("*");
                        mult.setLeftChild(new ConstNode(String.valueOf(num1 * num2)));
                        mult.setRightChild(root.getRightChild().getRightChild());

                        return mult;
                    }
                    if(root.getRightChild().getRightChild().getType().equals("constant")) { // Ex: 5*(x*6) ==> 30*x
                        int num1 = Integer.parseInt(root.getLeftChild().getVal());
                        int num2 = Integer.parseInt(root.getRightChild().getRightChild().getVal());
                        OpNode mult = new OpNode("*");
                        mult.setLeftChild(new ConstNode(String.valueOf(num1 * num2)));
                        mult.setRightChild(root.getRightChild().getLeftChild());

                        return mult;
                    }
                }
                if(root.getLeftChild().getType().equals("variable")) {
                    if(root.getRightChild().getLeftChild().getType().equals("variable")) {
                        OpNode exponent = new OpNode("^");
                        exponent.setLeftChild(root.getLeftChild());
                        exponent.setRightChild(new ConstNode("2"));
                        if(root.getRightChild().getRightChild().getType().equals("function")) { // Ex: x*(x*sin(x)) ==> (x^2)*sin(x)
                            root.setRightChild(root.getRightChild().getRightChild());
                            root.setLeftChild(exponent);
                        }
                        else {                                                                  // Ex: x*(x*5) ==> 5*x^2
                            root.setRightChild(exponent);
                            root.setLeftChild(root.getRightChild().getRightChild());
                        }

                        return root;
                    }
                    if(root.getRightChild().getRightChild().getType().equals("variable")) {
                        OpNode exponent = new OpNode("^");
                        exponent.setLeftChild(root.getLeftChild());
                        exponent.setRightChild(new ConstNode("2"));
                        if(root.getRightChild().getLeftChild().getType().equals("function")) { // Ex: x*(sin(x)*x) ==> (x^2)*sin(x)
                            root.setRightChild(root.getRightChild().getLeftChild());
                            root.setLeftChild(exponent);
                        }
                        else {
                            root.setRightChild(exponent);
                            root.setLeftChild(root.getRightChild().getLeftChild());            // Ex: x*(5*x) ==> 5*x^2
                        }

                        return root;
                    }
                }
            }
            else if(root.getLeftChild().getVal().equals("^") && root.getRightChild().getVal().equals("^")) {

            }
            else if(root.getLeftChild().getVal().equals("^")) {  // Ex: (x^2)*x

            }
            else if(root.getRightChild().getVal().equals("^")) { // Ex: x*(x^2)
                if(root.getLeftChild().getType().equals("variable") && root.getRightChild().getLeftChild().getType().equals("variable")) {

                }
            }
            else if(root.getLeftChild().getType().equals("unaryMinus")) {
                if(root.getRightChild().getType().equals("constant") && root.getLeftChild().getRightChild().getType().equals("constant")) {
                    int num1 = Integer.parseInt(root.getRightChild().getVal());
                    int num2 = Integer.parseInt(root.getLeftChild().getRightChild().getVal());
                    NegateNode unary = new NegateNode("$");
                    unary.setRightChild(new ConstNode(String.valueOf(num1 * num2)));

                    return unary;
                }
            }
            else if(root.getRightChild().getType().equals("unaryMinus")) {
                if(root.getLeftChild().getType().equals("constant") && root.getRightChild().getRightChild().getType().equals("constant")) {
                    int num1 = Integer.parseInt(root.getLeftChild().getVal());
                    int num2 = Integer.parseInt(root.getRightChild().getRightChild().getVal());
                    NegateNode unary = new NegateNode("$");
                    unary.setRightChild(new ConstNode(String.valueOf(num1 * num2)));

                    return unary;
                }
            }
            // Ex: x*x ==> x^2
            else if(root.getLeftChild().getType().equals("variable") && root.getRightChild().getType().equals("variable")) {
                OpNode exponent = new OpNode("^");
                exponent.setLeftChild(root.getLeftChild());
                exponent.setRightChild(new ConstNode("2"));

                return exponent;
            }
            else if(root.getLeftChild().getType().equals("constant") && root.getRightChild().getType().equals("constant")) {
                int num1 = Integer.parseInt(root.getLeftChild().getVal());
                int num2 = Integer.parseInt(root.getRightChild().getVal());

                return new ConstNode(String.valueOf(num1 * num2));
            }
        }
        else if(root.getVal().equals("+")) {
            if(root.getLeftChild().getVal().equals("0")) {
                return root.getRightChild();
            }
            else if(root.getRightChild().getVal().equals("0")) {
                return root.getLeftChild();
            }
        }
        else if(root.getVal().equals("^")) {
            if(root.getRightChild().getVal().equals("1")) {
                return root.getLeftChild();
            }
        }

        root.setLeftChild(simplify(root.getLeftChild()));
        root.setRightChild(simplify(root.getRightChild()));

        return root;
    }

    public String derive()
    {
        derivativeRoot = derivativeRoot.derivative();

        // need to change simplfy to do depth-first traversal so that the
        // tree will be fully "simplified" after one call
        derivativeRoot = simplify(derivativeRoot);
        derivativeRoot = simplify(derivativeRoot);
        derivativeRoot = simplify(derivativeRoot);

        return createInfix(derivativeRoot);
    }

    public int getPrecedence(String str)
    {
        int val = 0;

        if(str.equals("+"))
            val = 2;
        else if(str.equals("-"))
            val = 2;
        else if(str.equals("*") || str.equals(("/")))
            val = 3;
        else if(str.equals("^") || str.equals("$"))
            val = 4;

        return val;
    }

    public boolean isLeftAssociative(String s)
    {
        if(s.equals("^") || s.equals("$") || s.equals("+") || s.equals("*"))
            return false;

        return true;
    }

    // modifies the string to look more like it would if someone wrote the expression
    // out on paper
    public String readable(String s)
    {
        for(int i = 0; i < s.length(); i++) {
            if(s.substring(i, i + 1).equals("*") && i > 0)
                if(isOperand(s.substring(i - 1, i)) && i < s.length() - 1 && s.substring(i + 1, i + 2).equals(var))
                    s = s.substring(0, i) + s.substring(i + 1);
        }

        return s;
    }

    // adds and deletes characters to aid in the creation of the binary expression tree
    public String formatString(String exp)
    {
        exp = exp.replaceAll("\\s",""); // why
        exp = exp.toLowerCase();

        int count = 0;

        if(exp.substring(0, 1).equals("-")) { // if expression starts with a minus sign, it is a unary one
            exp = "$" + exp.substring(1); // replace
        }

        for(int i = 0; i < exp.length(); i++) {
            if(exp.substring(i,  i + 1).equals("("))
                count++;
            else if(exp.substring(i, i + 1).equals(")"))
                count--;
        }
        while(count > 0) {
            exp += ")";
            count--;
        }

        // At the operators, when the operator is "-" and it is preceded by another operator,
        // or preceded by a left parenthesis, or when it is the first character of the input
        // it is a unary minus rather than binary. In this case, I change it to another
        // character, '$', and make its precedence the same as that of '^'.
        for(int i = 0; i < exp.length() - 1; i++) {
            String tmp1 = exp.substring(i, i + 1);
            String tmp2 = exp.substring(i + 1, i + 2);
            if(tmp2.equals("-") && (isOperator(tmp1) || tmp1.equals("(")))
                exp = exp.substring(0, i + 1) + "$" + exp.substring(i + 2);
            else if((tmp1.matches("[0-9]+") || tmp1.equals(var)) && (tmp2.equals("(") || tmp2.equals(var)))
                exp = exp.substring(0, i + 1) + "*" + exp.substring(i + 1);
        }

        return exp;
    }

    //creates string representing infix expression
    public String createInfix(ExpNode root)
    {
        String str = "";

        if(root == null) {
            return str;
        }

        if (root != null) {

            if (root.getRightChild() == null) {
                str += root.getVal();

            }
            else if(root.getType().equals("function")) {
                str += root.getVal();
                //str += "(";

            }
            else if(root.getType().equals("unaryMinus")) {
                str += "-";
            }
            else {
                int parentPrecedence = getPrecedence(root.getVal());

                str += root.getVal();
                if(root.getLeftChild() != null && (getPrecedence(root.getLeftChild().getVal()) < parentPrecedence || isLeftAssociative(root.getLeftChild().getVal()))) {
                    //str += "(";
                }
                /*if(getPrecedence(root.getRightChild().getVal()) < parentPrecedence) {
                    str += ")";
                }*/
            }
        }

        return createInfix(root.getLeftChild()) + str + createInfix(root.getRightChild());
    }

    // separates the expression string into "tokens" and sorts them in
    // postfix order
    public ArrayList<String> tokenize(String exp)
    {
        ArrayList<String> tokens = new ArrayList<>();
        Stack<String> stack = new Stack<>();

        for(int i = 0; i < exp.length(); i++)
        {
            String token = "";

            if(isOperator(exp.substring(i, i + 1)) || exp.substring(i, i + 1).equals("$")) {
                token = exp.substring(i, i + 1);
                while ((!stack.isEmpty() && (isOperator(stack.peek()) || stack.peek().equals("$")))
                        && ((isLeftAssociative(token) && getPrecedence(token) <= getPrecedence(stack.peek()))
                        || (!isLeftAssociative(token) && getPrecedence(token) < getPrecedence(stack.peek())))) {

                    tokens.add(stack.pop());
                }
                stack.push(token);
            }
            else if(exp.substring(i, i + 1).equals(var)) {
                token = var;
                tokens.add(token);
            }
            else if(exp.substring(i, i + 1).equals("(")) {
                token = exp.substring(i, i + 1);
                stack.push(token);
            }
            else if(exp.substring(i, i + 1).equals(")")) {
                while(!stack.isEmpty() && !stack.peek().equals("(")) {
                    tokens.add(stack.pop());
                }

                if(!stack.isEmpty())
                    stack.pop();
                if(!stack.isEmpty() && isFunction(stack.peek())) {
                    tokens.add(stack.pop());
                }
            }
            else if(exp.substring(i, i + 1).matches("[0-9]+")) {
                while(i < exp.length() && exp.substring(i, i + 1).matches("[0-9]+")) {
                    token += exp.substring(i, i + 1);
                    i++;
                }
                tokens.add(token);
                i--; // i was left incremented after the while loop
            }
            else if(exp.substring(i, i + 1).equals(var)) {
                tokens.add(token);
            }
            else {
                while(i < exp.length() && exp.substring(i, i + 1).matches("[a-zA-Z]+")) {
                    token += exp.substring(i, i + 1);
                    i++;
                }

                if(token.length() != 0) {
                    stack.push(token);
                }
            }
        }

        while(!stack.isEmpty()) {
            tokens.add(stack.pop());
        }

        return tokens;
    }

    // reads the "tokens" in order from the list and builds a tree
    public ExpNode constructTree(ArrayList<String> postTokens)
    {
        ExpNode root = null;
        Stack<ExpNode> nodes = new Stack<>();

        for(String str: postTokens)
        {
            if(str.matches("[0-9]+")) {
                nodes.push(new ConstNode(str));
            }
            else if(str.equals(var))
                nodes.push(new VarNode(var));
            else if(!nodes.isEmpty() && isFunction(str)) {
                FuncNode function = new FuncNode(str);
                function.setRightChild(nodes.pop());
                nodes.push(function);
            }
            else if(!nodes.isEmpty() && str.equals("$")) {
                NegateNode unary = new NegateNode("$");
                unary.setRightChild(nodes.pop());
                nodes.push(unary);
            }
            else if(!nodes.isEmpty()){
                OpNode operator = new OpNode(str);
                operator.setRightChild(nodes.pop());
                operator.setLeftChild(nodes.pop());
                nodes.push(operator);
            }
        }

        if(!nodes.isEmpty())
            root = nodes.pop();

        return root;
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ Note: It doesn't look like your app supports real (floating-point) operands. BTW, you can rest assured someone did this worse than you did: my symbolic differentiation library is just glorified string replacement (but, hey, it works!). I also find that you could combine some if conditions together with Boolean operators. Unfortunately, your code is a bit too long for me to review at this time. Overall, this is a great effort! The comments are especially helpful! \$\endgroup\$ Commented May 26, 2017 at 10:11
  • \$\begingroup\$ You should be able to get rid of the getType() (and the ugly String type). If I have some time I'll write an answer \$\endgroup\$ Commented May 26, 2017 at 11:13

2 Answers 2

2
\$\begingroup\$

Restricted domain

    String operator;

Consider

    BinaryOperator operator;

Where

Enum BinaryOperator {

    PLUS("+"),
    MINUS("-"),
    MULTIPLY("*"),
    DIVIDE("/"),
    POWER("^");

    final private String display;

    BinaryOperator(String display) {
        this.display = display;
    }

    public String toString() {
        return display;
    }

}

This way, rather than having to do string evaluations to switch, you can just track the operations directly.

Naming

I would prefer a name like Expression to ExpNode. ExpNode tells the user how the structure is implemented. But a user shouldn't have to care about that. Logically, what you have is a representation of an expression.

A name like derivative is consistent with method names like iterator, but it is more typical to give methods verb names. E.g. calculateDerivative or takeDerivative.

Redundant fields

    OpNode mult1;
    OpNode mult2;
    OpNode mult3;
    OpNode div;
    OpNode plus;
    OpNode minus;
    OpNode exponent;
    NegateNode unary;

I didn't get why you had these at first. But reading through your derivative method, these are where you store the derivative. Consider instead

    private Expression derivative = null;

Then change from

            case "/": // h(x) = f(x)/g(x) then h′(x) = (f′(x)g(x) − f(x)g′(x))/( g(x)^2)
                mult1 = new OpNode("*");
                mult2 = new OpNode("*");
                mult3 = new OpNode("*");
                div = new OpNode("/");
                minus = new OpNode("-");

                mult1.setLeftChild(getRightChild());
                mult1.setRightChild(getLeftChild().derivative());
                mult2.setLeftChild(getLeftChild());
                mult2.setRightChild(getRightChild().derivative());
                minus.setLeftChild(mult1);
                minus.setRightChild(mult2);
                mult3.setLeftChild(getRightChild());
                mult3.setRightChild(getRightChild());
                div.setLeftChild(minus);
                div.setRightChild(mult3);

                return div;

to something like

            case "/":
                // h(x) = f(x)/g(x) then h′(x) = (f′(x)g(x) − f(x)g′(x))/( g(x)^2)
                Expression numerator = new BinaryExpression(DIFFERENCE);
                Expression denominator = new BinaryExpression(PRODUCT);

                Expression minuend = new BinaryExpression(PRODUCT);
                Expression subtrahend = new BinaryExpression(PRODUCT);

                minuend.setLeftChild(getLeftChild().takeDerivative());
                minuend.setRightChild(getRightChild());
                subtrahend.setLeftChild(getLeftChid());
                subtrahend.setRightChild(getRightChild().takeDerivative());

                numerator.setLeftChild(minuend);
                numerator.setRightChild(subtrahend);

                denominator.setLeftChild(getRightChild());
                denominator.setRightChild(getRightChild());

                derivative = new BinaryExpression(DIVISION);
                derivative.setLeftChild(numerator);
                derivative.setRightChild(denominator);

                return derivative;

Now you don't have to include every possible permutation as object fields. Only derivative persists past the length of the method. Everything else is a local variable specific to this operation.

I used operation specific names rather than generic names like left and right.

If you add

        if (derivative != null) {
            return derivative;
        }

Then you don't have to reprocess everything each time.

Or if you want to reprocess everything, then you can make derivative local as well.

More classes

You have a separate class for unary minus (NegateNode). Why not do that for other operations? Consider

class Quotient implements Expression {

    private final Expression numerator;
    private final Expression denominator;

    public Quotient(Expression numerator, Expression denominator) {
         this.numerator = numerator;
         this.denominator = denominator;
    }

    public Expression getNumerator() {
        return numerator;
    }

    public Expression getDenominator() {
        return denominator;
    }

    @Override
    public Expression takeDerivative() {
        return new Quotient(
                new Difference(
                        new Product(numerator.takeDerivative(), denominator),
                        new Product(numerator, denominator.takeDerivative()),
                    ),
                new Product(denominator, denominator)
            );
    }

    @Override
    public Expression simplify() {
        Expression n = numerator.simplify();
        Expression d = denominator.simplify();

        if (n.equals(Constant.ZERO) || d.equals(Constant.ONE)) {
            return n;
        }

        if (n.equals(d)) {
            return Constant.ONE;
        }

        if (d instanceof Quotient) {
            if (n instanceof Quotient) {
                return new Quotient(
                        new Product(n.getNumerator(), d.getDenominator()),
                        new Product(d.getNumerator(), n.getDenominator())
                    ).simplify();
            } else {
                return new Quotient(
                        new Product(n, d.getDenominator()),
                        d.getNumerator()
                    ).simplify();
            }
        } else if (n instanceof Quotient) {
            return new Quotient(
                    n.getNumerator(),
                    new Product(d, n.getDenominator())
                ).simplify();
        }

        return new Quotient(n, d);
    }

    @Override
    public Operator getOperator() {
        return DIVISION;
    }

    @Override
    public boolean equals(Object object) {
        if (! object instanceof Expression) {
            return false;
        }

        if (object == this) {
            return true;
        }

        Expression t = simplify();
        Expression o = ((Expression) object).simplify();

        if (! t instanceof Quotient) {
            return t.equals(o);
        }

        if (! o instanceof Quotient) {
            return false;
        }

        Quotient qt = (Quotient) t;
        Quotient qo = (Quotient) o;

        return qt.getNumerator().equals(qo.getNumerator())
                && qt.getDenominator().equals(qo.getDenominator());
    }

}

This may be missing a few methods, but it should cover the basics of the idea.

If you make separate classes for each operation, you don't need the switch anymore. Each class provides its own method instead. This can work for both takeDerivative and for simplify.

A side issue is that this eliminates all those object fields entirely through simple construction.

I made this immutable. You may not want to do so. Easy enough to remove, as it's just a few final keywords.

I switched the object fields to be private rather than the default package private. You don't seem to be making use of the package private privileges, and this is usually better practice. Similarly, I explicitly made methods public rather than relying on them being called from the same package.

I did not implement hashCode, but it might be advisable to do so. Otherwise, funky things could happen if you used this as a key in a HashMap, put it in a HashSet, or otherwise did something that relied on hashCode.

I may be missing simplification cases. For example, \$\frac{x^2 + x}{x}\$ would not simplify to \$x+1 \forall x \ne 0\$ with this method. These are the ones that came to mind with easy implementations.

\$\endgroup\$
4
  • \$\begingroup\$ This is some great insight, I appreciate it. Any recommendations on how to handle my simplify(remove redundant terms) method? To make it look cleaner, maybe I put it in the Expression class and make it a static method(so it could be used anywhere) \$\endgroup\$ Commented May 27, 2017 at 14:16
  • \$\begingroup\$ @defoification I edited the answer in reply. I didn't even see simplify originally, as I was thinking more about ExpNode. \$\endgroup\$
    – mdfst13
    Commented May 28, 2017 at 1:11
  • \$\begingroup\$ awesome answer. Did you mean BinaryOperator instead of BinaryExpression in this line Expression minuend = new BinaryExpression(PRODUCT); ? And since I want to implement Expression, where should I put my Enum ? \$\endgroup\$ Commented May 29, 2017 at 4:51
  • \$\begingroup\$ Or should I just have two interfaces, a BinaryExpression and a UnaryExpression. Maybe I just have public class Expression and then those two interfaces that extend Expression, and in ExpressionI have the enum. Sorry, I am very confused. I have never made anything with so many parts. \$\endgroup\$ Commented May 29, 2017 at 5:03
1
\$\begingroup\$

Some general things you should always consider:

  • Keep class and method size small

    The average size of your classes and methods is way to small. Try limiting the length drastically.
  • Make a class do one thing and only one thing

    This is more or less the single responsibility principle (https://en.wikipedia.org/wiki/Single_responsibility_principle). In particular, avoid having switches based on some internal variable state. Another thing this can be seen is when some variables aren't used depending on the case. Also use all variables when using polymorphism.
  • Don't expose internals of your classes

    The user of a ExpNode shouldn't have to worry about setting up the internal state with setLeftNode (what happens when someone forgets to do that? Make your design failsafe)
  • Use appropriate variable types

    Don't encode e.g. a number information into a string
  • Format your code consistently

    You changed the position of your parenthesis. The usual convention of Java is to have the opening parenthesis { on the same line.

ExpNode.java

According to the single responsibility principle I would propose the following class structure: The base class is a Expression. Subclasses involve BinaryOperation and UnaryOperation(i.e. Functions). All those classes are still abstract and you implement your concrete Classes as subclasses of those.

abstract class Expression {
    abstract Expression derive();
    abstract double getValue(double x);
}

I've kept it simple and required only two methods. The getValue method is used to calculate the value of the expression and derive calculates the deriviative. One could also add some methods like abstract String getDescription() for a textual representation or abstract Expression simplify() for simple simplifications.

abstract class BinaryOperation extends Expression
{
    protected Expression left;
    protected Expression right;

    BinaryOperation(Expression left, Expression right)
    {
        this.left = left;
        this.right = right;
    }
}

Note that the variables left and right are directly set in the constructor and a BinaryOperation cannot be in a "wrong" state.

class AdditionOperator extends BinaryOperation {
    AdditionOperator(Expression left, Expression right) {
        super(left, right);
    }

    @Override
    public Expression derive() {
        return new AdditionOperator(left.derive(), right.derive());
    }

    @Override
    double getValue(double x) {
        return left.getValue(x) + right.getValue(x);
    }
}

Note how simple the methods are now compared to the complicated design of having to check the operator and do something accordingly.

abstract class UnaryOperation extends Expression
{

    protected Expression exp;

    UnaryOperation(Expression exp)
    {
        this.exp = exp;
    }
}

Again, it isn't possible for the expression to be null. Setting up the class is done in the constructor.

class NaturalLogarithm extends UnaryOperation {

    @Override
    public Expression derive() {
        return new DivisionOperator(exp.derive(), exp); // I haven't implemented Division for you
    }

    @Override
    double getValue(double x) {
        return Math.log(x);
    }
}

This is an example of a function realized as a unary operation.

class Constant extends Expression
{
    double value;

    Constant(double value)
    {
        this.value = value;
    }

    Expression derive() { 
        return new Constant(0); 
        }

    @Override
    double getValue(double x) {
        return value;
    }
}

class Variable extends Expression
{
    Variable() {
    }

    Expression derive() {
        return new Constant(1);
    }

    @Override
    double getValue(double x) {
        return x;
    }
}

Variables and Constants are realized as direct subclasses of Expression

CalculusTool.java

After quickly skimming this class I'm not sure what the main purpose of this class is. It looks a bit like you just threw functionality into the class without thinking about how to group it logically. For example, without closely reading the code I'm completely baffled why there is public static String var = "";. Why do you use global state? What is the purpose of this variable? Then there are a bunch of static methods mixed with some non-static methods. Maybe you rework this class(i.e. extract it into different classes!) and post it in another code review.

Just one quick note I noticed in the method isValidExpression(String exp): I noticed you use some global state variable for the errorMessage. How about considering to rename the method to Evaluation evaluateExpression(String exp) and build a class Evaluation as follows:

class Evaluation {
    private boolean isValid;
    private List<String> errorMessages;
    // constructor and getters
}

MathApp.java

I kept my refactoring here also quite basic. I only extracted some methods in order to keep them shorter. A good refactoring here can only be done after CalculusTool is refactored. Just a pointer though: Again you use some String expression for handling the state. Consider using another event in order to track if there have been changes to the textfield.

public class MathApp extends Application {

    private CalculusTool cT;
    private String expression;
    private String deriv;

    private TextField expBox;
    private Text errorText;
    private Text actionTarget;

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("MathApp");

        GridPane grid = constructGrid();

        Scene scene = new Scene(grid, 300, 275);
        primaryStage.setScene(scene);

        primaryStage.show();
    }

    private GridPane constructGrid() {
        GridPane grid = constructGridPane();

        addExpressionText(grid);
        expBox = constructAndAddExpBox(grid);
        errorText = constructAndAddErrorText(grid);
        Button btn = addDeriveButton(grid);
        actionTarget = addActionTarget(grid);
        btn.setOnAction(this::handleButtonClick);
        return grid;
    }

    public void handleButtonClick(ActionEvent e) {

        //same expression - derive again when clicked
        if(expBox.hasProperties() && expBox.getText().equals(expression)) {
            deriveAndUpdateUI();
        }
        else { // new expression, create new object
            expression = expBox.getText();
            if (!CalculusTool.isValidExpression(expression)) {
                printErrorMessage();
            } else {
                errorText.setVisible(false);
                cT = new CalculusTool(expression);
                deriveAndUpdateUI();
            }
        }
    }

    private void printErrorMessage() {
        errorText.setText(CalculusTool.errorMessage);
        actionTarget.setVisible(false);
        errorText.setVisible(true);
        expBox.clear();
    }

    private void deriveAndUpdateUI() {
        deriv = cT.readable(cT.derive());

        actionTarget.setVisible(true);
        actionTarget.setFill(Color.FIREBRICK);
        actionTarget.setText(deriv);
    }


    private Text addActionTarget(GridPane grid) {
        final Text actiontarget = new Text();
        grid.add(actiontarget, 1, 6);
        return actiontarget;
    }


    private Button addDeriveButton(GridPane grid) {
        Button btn = new Button("Derive");
        HBox hbBtn = new HBox(10);
        hbBtn.setAlignment(Pos.BOTTOM_RIGHT);
        hbBtn.getChildren().add(btn);
        grid.add(hbBtn, 1, 4);
        return btn;
    }


    private Text constructAndAddErrorText(GridPane grid) {
        Text error = new Text();
        grid.add(error, 1, 6);
        error.setVisible(false);
        return error;
    }


    private TextField constructAndAddExpBox(GridPane grid) {
        TextField expBox = new TextField();
        grid.add(expBox, 1, 2);
        return expBox;
    }


    private void addExpressionText(GridPane grid) {
        Text scenetitle = new Text("Enter an expression");
        scenetitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 15));

        grid.add(scenetitle, 0, 0, 2, 1);
    }


    private GridPane constructGridPane() {
        GridPane grid = new GridPane();
        grid.setAlignment(Pos.CENTER);
        grid.setHgap(10);
        grid.setVgap(10);
        grid.setPadding(new Insets(25, 25, 25, 25));
        return grid;
    }


    public static void main(String[] args) {
        launch(args);
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.