ChengJade4.java

import java.io.FileReader;
import java.util.ArrayList;
import java.util.Scanner;
 
/**
 * A class that implements ICS 211 Homework 4, Infix Calculator. The infix
 * calculator accepts input from a file specified as a command-line parameter.
 * It reads each line of the file. For each line, it considers each character of
 * the line, one at a time, taking the appropriate action for each character.
 *
 * @author     Cheng Jade
 * @assignment TA 4
 * @date       Feb 08, 2010
 * @bugs       None
 */
public class ChengJade04 {
 
  /**
   * Compares two operators. This method does not check to see if the precedence
   * operators are valid.
   *
   * @param lhs The operator on the left hand side.
   * @param rhs The operator on the right hand side.
   * @return A value greater than zero if the operator on the left hand side is
   *         greater than the operator on the right hand side, a value less than
   *         zero if the operator on the right hand side is greater than the
   *         operator on the left hand side, or zero if the two operators have
   *         the same precedence.
   */
  private static int compareOperator(final char lhs, final char rhs) {
    // return the difference in the operator precedence values.
    return getPrecedence(lhs) - getPrecedence(rhs);
  }
 
 
  /**
   * Gets an integer value that represent's an operator's precedence. This
   * method does not check to see if the precedence operator is valid.
   *
   * @param op The operator.
   * @return An integer value that represent's the operator's precedence.
   *         Multiplication, division, and modulus have the same precedence
   *         values, and they are higher in precedence than addition and
   *         subtraction, which have the same precedence value.
   */
  private static int getPrecedence(final char op) {
    // return 1 for the add and minus operators.
    if ("+-".indexOf(op) >= 0)
      return 1;
 
    // return 2 for multiply, divide, and modulus operators.
    assert "*/%".indexOf(op) >= 0;
    return 2;
  }
 
 
  /**
   * Computes the result of an operation on two values. This method does not
   * check to see if the operator is valid.
   *
   * @param lhs The value on the left hand side of the operator.
   * @param op The operator.
   * @param rhs The value on the right hand side of the operator.
   * @return The result from the operator.
   * @throws CalculatorException Thrown if there is division or modulus by zero.
   */
  private static int compute(final int lhs, final char op, final int rhs)
      throws CalculatorException {
 
    // check for overflow while calculating the results.
    if (op == '+')
      return checkOverflow((long)lhs + (long)rhs);
 
    if (op == '*')
      return checkOverflow((long)lhs * (long)rhs);
 
    if (op == '-')
      return checkOverflow((long)lhs - (long)rhs);
 
    // check for division by zero.
    if (rhs == 0)
      throw new CalculatorException("Division by zero.");
 
    if (op == '/')
      return checkOverflow((long)lhs / (long)rhs);
 
    assert op == '%';
    return checkOverflow((long)lhs % (long)rhs);
  }
 
 
  /**
   * Pops two values off a number stack, computes the result based on the
   * operator, and return the result. This method does not check to see if the
   * operator is valid.
   *
   * @param numberStack The number stack.
   * @param op The operator.
   * @throws CalculatorException Thrown if there is division by zero or if the
   *           number stack is empty.
   */
  private static void popComputePush(final Stack<Integer> numberStack,
      final char op) throws CalculatorException {
 
    // pop two values off the stack, compute the result, and return the result.
    final int rhs = numberStack.pop();
    final int lhs = numberStack.pop();
    final int k = compute(lhs, op, rhs);
    numberStack.push(k);
  }
 
 
  /**
   * Evaluate if the operator stack and number stack are valid.  The number
   * stack should always have more or same number of elements versus the
   * operator stack for a valid infix calculator expression.
   *
   * @param numberStack The number stack.
   * @param operatorStack The operator stack.
   *
   * @return True if the stacks are valid or false if otherwise.
   */
  private static boolean validStacks(Stack<Integer> numberStack, Stack<Character> operatorStack) {
    return !(operatorStack.getSize() > numberStack.getSize());    
  }
 
 
  /**
   * This is the main entry point of the application.
   *
   * @param args The command line arguments. The user should specify a file with
   *          a list of lines to calculate.
   */
  public static void main(final String[] args) {
    // declare two stacks, one for the numbers, one for the operators.
    final Stack<Integer> numberStack = new Stack<Integer>();
    final Stack<Character> operatorStack = new Stack<Character>();
 
    // check if only one file is passed in as the command line input.
    if (args.length != 1) {
      System.err.println("usage: ChengJade04 <path>");
      System.exit(1);
      return;
    }
 
    try {
      // declare a FileReader to read the input file and also declare a Scanner
      // to read through the FileReader.
      final FileReader reader = new FileReader(args[0]);
      final Scanner scanner = new Scanner(reader);
 
      // the scanner loops through the file while there are more lines to read.
      while (scanner.hasNextLine()) {
        // initialize a String for the next line of the file.
        final String line = scanner.nextLine();
 
        // ignore the line if it's a blank line.
        if (line.length() == 0)
            continue;
 
        // check for exceptions here only to display the line that failed in the
        // outer catch block.
        try {
          // loop through each line checking each character from left to right.
          for (int i = 0; i < line.length(); i++) {
 
            // at any state, the operator stack can't have more elements than the 
            // number stack for a valid infix calculator expression.
            if (!validStacks(numberStack, operatorStack)) {
              System.err.println("Invalid Infix Calculator.");
              break;
            }
 
            final char c = line.charAt(i);
 
            // if it's a number, push it into the number stack.
            if (Character.isDigit(c)) {
              numberStack.push(c - '0');
              continue;
            }
 
            // if it's a space, ignore it and move on to the next character.
            if (Character.isSpaceChar(c))
              continue;
 
            // check for an invalid character.
            if ("+-*/%".indexOf(c) < 0)
              throw new Exception("Invalid character: '" + c + "'");
 
            // loop until the operator stack is empty or if the new operator is
            // greater than the top operator.
            while (!operatorStack.empty()) {
              // check if the top operator is less than or equal to the new
              // operator in precedence.
              final char topOperator = operatorStack.pop();
              if (compareOperator(c, topOperator) > 0) {
                // in this case push the operator back onto the stack and stop
                // looping
                operatorStack.push(topOperator);
 
                break;
              }
              // pop two numbers off the stack, compute their values, and
              // repeat.
              popComputePush(numberStack, topOperator);
            }
 
            // push the new operator onto the stack.
            operatorStack.push(c);
          }
 
          // now that end of the line was reached, loop until there are no more
          // operators on the stack.
          while (!operatorStack.empty()) {
            // pop two numbers off the stack, compute their values, and repeat.
            final char topOperator = operatorStack.pop();
            popComputePush(numberStack, topOperator);
          }
 
          // check that there is only one left value on the stack.
          if (numberStack.getSize() != 1)
            throw new CalculatorException("Not enough operators.");
 
          // display the line and the result.
          final int result = numberStack.pop();
          System.out.println("'" + line + "' = " + result);
 
        } catch (final Exception e) {
          // rethrow the exception to display the line that caused the failure.
          throw new CalculatorException("Error on line '" + line + "': "
              + e.getMessage(), e);
        }
      }
 
    } catch (final Exception e) {
      // in case of any errors, display the message, and exit with a failure.
      System.err.println(e.getMessage());
      System.exit(1);
      return;
    }
 
    // return zero to let the eclipse debugger close happily
    System.exit(0);
  }
 
 
  /**
   * Checks to see if the parameter (of type long) passed in is in the range of
   * an integer type. If it is, the same value is returned casted back to an
   * integer.
   *
   * @param n the number to be checked.
   * @return The same value casted back to an integer.
   * @throws CalculatorException Thrown if the value is not in the range of an
   *           integer type.
   */
  private static int checkOverflow(final long n) throws CalculatorException {
    // check for underflows.
    if (n < Integer.MIN_VALUE)
      throw new CalculatorException("Integer underflow encountered: " + n);
 
    // check for overflows.
    if (n > Integer.MAX_VALUE)
      throw new CalculatorException("Integer overflow encountered: " + n);
 
    // return the same value casted back as an integer type.
    return (int)n;
  }
}
 
/**
 * A generic stack class.
 */
class Stack<T> {
  /** An array to store the stack elements. */
  private final ArrayList<T> array = new ArrayList<T>();
 
 
  /**
   * Pushes an item onto the stack.
   *
   * @param item the item to be pushed onto the stack.
   */
  public void push(final T item) {
    assert item != null;
    array.add(item);
  }
 
 
  /**
   * Pop an item off the top of the stack.
   *
   * @return the last item that was pushed onto the stack.
   * @throws CalculatorException Thrown if the stack is empty.
   */
  public T pop() throws CalculatorException {
    // check the stack is not empty.
    if (array.isEmpty())
      throw new CalculatorException("The stack is empty.");
 
    // remove the last item added to the stack and return it.
    final int top = array.size() - 1;
    final T result = array.get(top);
    array.remove(top);
    return result;
  }
 
 
  /**
   * Returns true if the stack is empty.
   *
   * @return True if the stack is empty or false if the stack is not empty.
   */
  public boolean empty() {
    return array.isEmpty();
  }
 
 
  /**
   * Returns the size of the stack.
   *
   * @return the number of items that can currently be popped off the stack.
   */
  public int getSize() {
    return array.size();
  }
 
 
  /**
   * Returns the contents of the stack as a string.
   *
   * @return the contents of the stack as a string.
   */
  @Override
  public String toString() {
    // return a special message if the stack is empty.
    if (array.size() == 0)
      return "The stack is empty.";
 
    // build a list of items.
    final StringBuilder builder = new StringBuilder();
    builder.append("{ ");
    for (int i = 0; i < array.size(); i++)
      builder.append(array.get(i) + " ");
    builder.append("}");
 
    // return the list of items that looks like "{ a b c ... }".
    return builder.toString();
  }
}
 
/**
 * An exception class that is thrown and caught by the program.
 */
@SuppressWarnings("serial")
class CalculatorException extends Exception {
  /**
   * Initializes a new instance of the class.
   *
   * @param message The message.
   */
  public CalculatorException(final String message) {
    super(message);
  }
 
 
  /**
   * Initializes a new instance of the class.
   *
   * @param message The message.
   * @param throwable The object that caused the exception.
   */
  public CalculatorException(final String message, final Throwable throwable) {
    super(message, throwable);
  }
}
 
Valid HTML 4.01 Valid CSS