Java tutorial
/** 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"; } }