us.mschmidt.komo.CalcEngine.java Source code

Java tutorial

Introduction

Here is the source code for us.mschmidt.komo.CalcEngine.java

Source

/**
  The following code is from Michael Schmidt(MichaelMSchmidt (at) msn.com).
      
  The code is published under BSD license.
      
  Thanks for the input from Michael Schmidt.
*/

package us.mschmidt.komo;

/**
 * This is a generic calculator engine that includes a memory register, some
 * numeric conversions (square root, inverse), and the ability to chain 
 * calculations.  To use it, create a GUI shell with a display and some means 
 * of entering keystrokes (Buttons work well).  Invoke this engine from the GUI
 * shell using the constructor method, which requires that the display size 
 * (String length) be set.  Obtain the initial display, if desired, with the 
 * getDisplayString() method.  Subsequently, the display string is returned by 
 * the setInput() method when an operation (character) is passed to the engine.
 * <p>
 * The constructor requires a display size to be set.  The minimum is three
 * characters, with at least 22 characters recommended and recommended  size 
 * being 30 characters.  
 *  
 * @author    Michael Schmidt
 * @version   1.1
 */
class CalcEngine {

    // Instantiatee constants
    private final String calcChars;
    private final String convChars;
    private final String dispChars;
    private final String memChars;
    private final String errChars;

    // Instantiate variables
    // The error messages
    private String nanErr = new String();
    private String tooLongErr = new String();
    private String infinityErr = new String();
    private int displaySize;

    // The calculator 'registers'
    private String displayString = new String();
    private String memoryString = new String();
    private String operatorString = new String();

    // A boolean to indicate if display will be cleared on the next key entry
    private boolean clearDisplay;

    // A character to store the pending calculation
    private char calcChar;

    /**
     * Constructor to create the calculator engine object.
     * 
     * @param displaySizeVal   The GUI display size, minimum of 3 and
     *                                     recommended size of 30
     */
    CalcEngine(final int displaySizeVal) {
        final int minSize = 3;
        // Initialize operation characters
        calcChars = "+-/*=";
        convChars = "IQ";
        dispChars = "BCERS.0123456789";
        memChars = "DLMP";
        errChars = "EINO*";

        // Set initial display and internal variables
        displayString = "0.";
        clearDisplay = true;
        calcChar = ' ';

        // Set the display size
        displaySize = minSize;
        if (displaySizeVal > minSize) {
            displaySize = displaySizeVal;
        }
        // Customize the error strings based on the display size
        setErrorStrings();
    }

    /**
     * Customizes the error messages based on the display size.
     */
    private void setErrorStrings() {

        // Initialize constants for display size thresholds
        final int infLength = 8;
        final int nanLength = 12;
        final int tooLongLength = 15;
        final int fullLength = 22;

        // Set default strings for optimal display size
        nanErr = "Not a Number";
        tooLongErr = "Number too long";
        infinityErr = "Infinity";

        if (displaySize < infLength) {
            nanErr = "NaN";
            tooLongErr = "***";
            infinityErr = "Inf";
        } else if (displaySize < nanLength) {
            nanErr = "NaN";
            tooLongErr = "Overflow";
        } else if (displaySize < tooLongLength) {
            tooLongErr = "Overflow";
        } else if (displaySize >= fullLength) {
            nanErr = "ERROR: " + nanErr;
            tooLongErr = "ERROR: " + tooLongErr;
            infinityErr = "ERROR: " + infinityErr;
        }
    }

    /**
     * Provides the display string to the calling class.  Used after the  
     * engine is instantiated.  Subsequently, the display string is returned by
     * the setInput() method.
     * 
     * @return   the display String
     */
    public String getDisplayString() {
        return displayString;
    }

    /**
     * Allows entry of keystrokes from the calling class.
     * 
     * @param keyVal   A designation of the key pressed by the user using the
     *                         code.  
     *                         Display codes: B backspace, C clear, E clear entry,
     *                         R recall memory to display, S change sign, . decimal, 
     *                         0-9 numeric entries.
     *                         Memory codes: D clear memory, M subtract from memory, 
     *                         L store in memory, P add to memory. 
     *                         Calculate codes: I inverse, Q square root, S subtract.
     * @return            boolean true if the display has changed, false if not
     */
    public String setInput(final char keyVal) {
        final char keyChar = keyVal;
        if (dispChars.indexOf(keyChar) != -1) {
            doDisplayOp(keyChar);
        } else if (convChars.indexOf(keyChar) != -1) {
            doConvertOp(keyChar);
        } else if (memChars.indexOf(keyChar) != -1) {
            doMemoryOp(keyChar);
        } else if (calcChars.indexOf(keyChar) != -1) {
            doCalcOp(keyChar);
        }
        return displayString;
    }

    /**
     * Clears the display when 1) the clearDisplay flag had previously been set
     * to 'true' and 2) a new character or calculation operation is entered.
     */
    private void doDisplayClear() {
        if (clearDisplay) {
            displayString = "";
            clearDisplay = false;
        }
    }

    /**
     * Performs number conversion operations: inverse (1/X) and square root.
     * 
     * @param keyVal   a designation of the key pressed by the user
     */
    private void doConvertOp(final char keyVal) {
        final char opChar = keyVal;
        String tempString = doConvert(displayString, opChar);
        clearDisplay = true;
        if (tempString.length() > 0) {
            displayString = tempString;
        }
    }

    /**
     * Performs display operations.  Simple assignment operations are performed 
     * in this method while more complex operations are performed in sub-methods. 
     * 
     * @param keyVal   a designation of which key was pressed by the user
     */
    private void doDisplayOp(final char keyVal) {
        final char opChar = keyVal;

        switch (opChar) {
        case 'B': // Backspace
            doBackspace();
            break;

        case 'C': // Clear.  Note use of dropthrough
            operatorString = "";
            calcChar = ' ';

        case 'E': // Clear Entry
            displayString = "0.";
            clearDisplay = true;
            break;

        case 'R': // Recall Memory to Display
            displayString = memoryString;
            break;

        case 'S': // Change Sign
            doChangeSign();
            break;

        case '.': // Can't have two decimal points.
            doDecimal(opChar);
            break;

        case '0': // Don't want 00 to be entered.
            doZero(opChar);
            break;

        default: // Default case is for the digits 1 through 9.
            doAddChar(opChar);
            break;
        }
    }

    /**
     * Performs backspace operation.
     */
    private void doBackspace() {
        if (isError(displayString)) {
            return;
        }
        if (displayString.length() > 0) {
            displayString = displayString.substring(0, displayString.length() - 1);
        }
    }

    /**
     * Performs operation to add a character to the display.
     * 
     * @param c   the character to add
     */
    private void doAddChar(final char c) {
        if (isError(displayString)) {
            return;
        }
        if (displayString.length() < displaySize) {
            doDisplayClear();
            displayString += c;
        }
    }

    /**
     * Performs sign change operation.
     */
    private void doChangeSign() {
        if (isError(displayString)) {
            return;
        }
        if ('-' == displayString.charAt(0)) {
            displayString = displayString.substring(1, displayString.length());
        } else if (displayString.length() < displaySize) {
            displayString = '-' + displayString;
        }
    }

    /**
     * Performs operation when decimal is pressed.
     * 
     * @param c   the decimal character
     */
    private void doDecimal(final char c) {
        if (isError(displayString)) {
            return;
        }
        if (displayString.indexOf('.') == -1 && displayString.length() < displaySize) {
            doDisplayClear();
            displayString += c;
        }
    }

    /**
     * Performs operation when '0' is pressed.
     * 
     * @param c   the zero operation character
     */
    private void doZero(final char c) {
        if (isError(displayString)) {
            return;
        }
        if (!displayString.equals("0") && displayString.length() < displaySize) {
            doDisplayClear();
            displayString += c;
        }
    }

    /**
     * Updates the value stored in the memory register.  Simple assignment 
     * operations are performed here while more complex operations are performed 
     * in sub-methods.
     * 
     * @param keyVal   a designation of which key was pressed by the user
     */
    private void doMemoryOp(final char keyVal) {
        final char opChar = keyVal;

        switch (opChar) {
        case 'D': // Clear Memory
            memoryString = "";
            break;

        case 'L': // Save to Memory
            assignMemoryString(trimString(displayString));
            break;

        case 'M': // Subtract from Memory
            subtractFromMemory();
            break;

        case 'P': // Add to Memory
            addToMemory();
            break;

        default: // Do nothing - this should never happen.
            break;
        }

        clearDisplay = true;
    }

    /**
     * Performs the operation to add a display entry to the number in the
     * memory register.
     */
    private void addToMemory() {
        String tempString = new String();
        if (0 == memoryString.length()) {
            tempString = trimString(displayString);
        } else {
            tempString = doComputation(memoryString, displayString, '+');
        }
        assignMemoryString(tempString);
    }

    /**
     * Performs the operation to subtract a display entry from the number in the
     * memory register.
     */
    private void subtractFromMemory() {
        String tempString = new String();
        if (0 == memoryString.length()) {
            tempString = doComputation("0", displayString, '-');
        } else {
            tempString = doComputation(memoryString, displayString, '-');
        }
        assignMemoryString(tempString);
    }

    /**
     * Checks if String is valid and, if so, assigns it to the memory register.
     * 
     * @param s   the String to assign
     */
    private void assignMemoryString(final String s) {
        if (isError(s)) {
            displayString = s;
        } else {
            memoryString = s;
        }
    }

    /**
     * Guides 2-number calculations.  Updates the operator string, display 
     * string, and the pending calculation flag.  Performs calculation if 
     * possible.
     * 
     * @param keyVal   a designation of which key was pressed byt he user
     */
    private void doCalcOp(final char keyVal) {
        final char opChar = keyVal;

        // If there is no display value, the keystroke is deemed invalid and 
        // nothing is done.
        if (0 == displayString.length()) {
            return;
        }

        // If there is no operator value, '=' key presses are considered 
        // invalid.  If a calculation key is pressed, check that the display 
        // value is valid and if so, copy the display value to the operator.  
        // No calculation is done.
        if (0 == operatorString.length()) {
            if ('=' != opChar) {
                if (isError(displayString)) {
                    calcChar = ' ';
                } else {
                    operatorString = displayString;
                    calcChar = opChar;
                }
                clearDisplay = true;
            }
            return;
        }

        // There are operator and display values, so do the pending calculation.
        displayString = doComputation(operatorString, displayString, calcChar);

        // If '=' was pressed or result was invalid, reset pending calculation
        // flag and operator value.  Otherwise, set new calculation flag so 
        // calculations can be chained.
        if (('=' == opChar) || isError(displayString)) {
            calcChar = ' ';
            operatorString = "";
        } else {
            calcChar = opChar;
            operatorString = displayString;
        }

        // Set the clear display flag
        clearDisplay = true;
    }

    /**
     * Performs the computations.
     * 
     * @param numStringA   the displayed value
     * @param numStringB   the stored value
     * @param opVal            the operation to be performed
     * @return                  the solution as a string variable
     */
    private String doComputation(final String numStringA, final String numStringB, final char opVal) {
        String valStringA = numStringA;
        String valStringB = numStringB;
        char opChar = opVal;
        Double valA = 0.0;
        Double valB = 0.0;
        Double valAnswer = 0.0;

        // Make sure register strings are numbers
        if (valStringA.length() > 0 && valStringB.length() > 0) {
            try {
                valA = Double.parseDouble(numStringA);
                valB = Double.parseDouble(numStringB);
            } catch (final NumberFormatException e) {
                return nanErr;
            }
        } else {
            return "";
        }

        switch (opChar) {
        case '+': // Addition
            valAnswer = valA + valB;
            break;

        case '-': // Subtraction
            valAnswer = valA - valB;
            break;

        case '/': // Division
            valAnswer = valA / valB;
            break;

        case '*': // Multiplication
            valAnswer = valA * valB;
            break;

        default: // Do nothing - this should never happen
            break;
        }

        // Convert answer to properly formatted string.
        return trimString(valAnswer.toString());
    }

    /**
     * Performs number conversion computations.
     * 
     * @param numString   the number to be converted
     * @param opVal      designation of the operation to be performed
     * @return         the converted value
     */
    private String doConvert(final String numString, final char opVal) {
        char opChar = opVal;
        String valString = numString;
        Double valA = 0.0;
        Double valAnswer = 0.0;

        // Make sure String is a number.  If it is zero-length, assume the
        // keystroke was inadvertent and return "".
        if (valString.length() > 0) {
            try {
                valA = Double.parseDouble(valString);
            } catch (final NumberFormatException e) {
                return nanErr;
            }
        } else {
            return "";
        }

        switch (opChar) {
        case 'Q': // Square Root
            valAnswer = Math.sqrt(valA);
            break;

        case 'I': // Inverse
            valAnswer = 1.0 / valA;
            break;

        default: // Do nothing - this should never happen
            break;
        }

        // Return properly formatted result String.
        return trimString(valAnswer.toString());
    }

    /**
     * Formats String to be displayed.
     * 
     * @param stringVal   a new string to be displayed
     * @return               the properly formatted and trimmed string
     */
    private String trimString(final String stringVal) {
        String returnString = stringVal;

        // Check if value is Not a Number
        if (returnString.equals("NaN")) {
            return nanErr;
        }

        // Check if value is infinity
        if (returnString.endsWith("Infinity")) {
            return infinityErr;
        }

        // Check if value is -0
        if (returnString.equals("-0.0")) {
            return "0";
        }

        // Trim unnecessary trailing .0
        if (returnString.endsWith(".0")) {
            returnString = returnString.substring(0, returnString.length() - 2);
        }

        // Check if string is too long to display
        if (returnString.length() > displaySize) {
            return tooLongErr;
        }

        return returnString;
    }

    /**
     * Tests if the String is an error message.
     * 
     * @param s   the String to be tested
     * @return   boolean true if this is an error message, false if not
     */
    private boolean isError(final String s) {
        String testString = s;
        if (0 == testString.length()) {
            return false;
        }
        char c = testString.charAt(0);
        return ((errChars.indexOf(c) == -1)) ? false : true;
    }

    /**
     * Override to provide useful information when the class toString method is
     * called.
     * 
     * @return   the information String
     * @see       java.lang.Object#toString()
     */
    @Override
    public final String toString() {
        return "komo.CalcEngine is the engine portion of the calculator utility";
    }
}