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