Mega Code Archive

 
Categories / Java / SWT Jface Eclipse
 

Engine for Calculator by Michael Schmidt

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