/*
 * @(#)Operation.java  2.5.0  30 July 2006
 *
 * Copyright 2006
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.parser;

import java.text.ParseException;

/**
 * <p>Class <code>Operation</code> encapsulates an operation
 * and its corresponding <code>String</code> symbol 
 * for use with a parser.</p>
 *
 * <p>This class is <i>abstract</i> since the method
 * <code>performOperation</code> must be defined.</p>
 * 
 * <p>As of 2.5.0, this class was extracted from the class
 * <code>AbstractParser</code> and made standalone.</p>
 *
 * @author  Richard Rasala
 * @author  Jeff Raab
 */ 
public abstract class Operation {
    
    /** 
     * <p>The IDENTITY operation, equivalent to the function 
     * <I>f</I>(<I>x</I>,<I>y</I>)=<I>y</I>.</p>
     * 
     * <p>In the class <code>AbstractParser</code>, this operation
     * is inserted with the lowest level of precedence and is used
     * as the base for evaluation of an expression.</p>
     */
    public static final Operation IDENTITY = new Operation("") {
        public Object performOperation(Object left, Object right)
        {
            return right;
        }
    };
    
    
    /** 
     * <p>The singleton operation object which designates that
     * a symbol is a prefix for a known operation.</p>
     * 
     * <p>This operation operates as a return flag and may not
     * be inserted into the precedence structure of the class
     * <code>AbstractParser</code>.</p>
     */
    public static final Operation OPERATION_PREFIX = new Operation("\0") {
        public Object performOperation(Object left, Object right)
        {
            return null;
        }
    };
    
    
    /** The symbol representing this operation. */
    private String symbol = "";
    
    /**
     * Whether or not the operation can act as a unary operation.
     * 
     * The default value is true.
     */
    private boolean isUnary = true;
    
    /**
     * Whether or not the operation can act as a binary operation.
     *
     * The default value is true.
     */
    private boolean isBinary = true;
    
    
    //////////////////
    // Constructors //
    //////////////////
    
    /**
     * <p>Constructs an operation with the given symbol.</p>
     *
     * <p>The symbol "" is reserved for IDENTITY.</p>
     * 
     * <p>The symbol "\0" is reserved for OPERATION_PREFIX.</p>
     * 
     * <p>Throws <code>IllegalArgumentException</code>
     * if the operation id is not valid when tested by
     * <code>AbstractParser.isPossibleOperation</code>.</p>
     *
     * @param id the symbol for the operation
     * @throws IllegalArgumentException
     */
    public Operation(String id) {
        if (id == null) {
            String message = "null id passed"
            + " when defining an operation";
        
            throw new IllegalArgumentException(message);
        }
        
        if (! BaseParser.isPossibleOperation(id)) {
            String message = "\"" + id + "\" is not a valid symbol"
                + " for defining an operation";
            
            throw new IllegalArgumentException(message);
        }
        
        symbol = id;
    }
    
    
    /**
     * Constructs an operation with the given symbol and settings
     * for unary and binary usage.
     *
     * <p>The symbol "" is reserved for IDENTITY.</p>
     * 
     * <p>The symbol "\0" is reserved for OPERATION_PREFIX.</p>
     * 
     * <p>Throws <code>IllegalArgumentException</code>
     * if the operation id is not valid when tested by
     * <code>AbstractParser.isPossibleOperation</code>.</p>
     *
     * @param id     the symbol for the operation
     * @param unary  whether the operation may be unary
     * @param binary whether the operation may be binary
     * @throws IllegalArgumentException
     */
    public Operation(String id, boolean unary, boolean binary) {
        this(id);
        
        isUnary  = unary;
        isBinary = binary;
    }
    
    
    /////////////
    // Methods //
    /////////////
    
    /**
     * <p>Performs the operation on the given values
     * and returns the result.</p>
     *
     * @param left  the left side operand for a binary operation
     *              or <code>null</code> for a unary operation
     * @param right the right side operand for a unary or binary
     *              operation
     */
    public abstract Object performOperation(Object left, Object right) 
        throws ParseException;
    
    
    /**
     * Returns the symbol for this operation.
     */
    public final String symbol() {
        return symbol;
    }
    
    
    /**
     * Returns true if the operation may act as a unary operation.
     */
    public final boolean isUnary() {
        return isUnary;
    }
    
    
    /**
     * Returns true if the operation may act as a binary operation.
     */
    public final boolean isBinary() {
        return isBinary;
    }
    
    
    /** Throws parseException if operation cannot act as unary. */
    public final void checkUnary()
        throws ParseException 
    {
        if (!isUnary) {
            throw new ParseException(
                "Operation "
                + symbol
                + " expects 2 arguments."
                , 0);
        }
    }
    
    /** Throws parseException if operation cannot act as binary. */
    public final void checkBinary()
        throws ParseException 
    {
        if (!isBinary) {
            throw new ParseException(
                "Operation "
                + symbol
                + " expects 1 argument."
                , 0);
        }
    }
    
}

