Engine for Calculator by Michael Schmidt : Calculator « SWT JFace Eclipse « Java






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


           
       








Related examples in the same category

1.Calculator by Michael SchmidtCalculator by Michael Schmidt