/* Title:		OperationObject.java
 * Version	
 * Author		Sada
 * Company: USDA-ARS
 * Date:    July, 2003
 * Description: This class holds data of an operation (throug actions).
 * 
 */
package usda.weru.mcrew;

import de.schlichtherle.io.File;
import de.schlichtherle.io.FileWriter;
import java.util.*;

import org.apache.log4j.Logger;


import org.w3c.dom.*;
import org.w3c.dom.traversal.*;
import org.apache.xml.serialize.*;
import usda.weru.util.*;

/**
 * This class holds data of an operation (throug actions).
 */
public class OperationObject extends DataObject implements Cloneable
{
    private static final Logger LOGGER = Logger.getLogger(OperationObject.class);
	private Vector <Action> mActions; //Holds as many Actions (with data values) as required
    /**
     * The name of the operation whose data this object holds.
     */    
	public String mOperationName; // Name of this particular operation
	
	private boolean mCropPresent;
    
    /** This Vector stores the identites of all actions for quick reference.
	* Also,  the vector is used to maintain the order of actions in this operationObject. Note that, Hashtables
	* do not maintain the order */
	Vector mActionIdVec; 
    
    /**
     * Returned if the file is read successfully.
     */
    public static final int kSuccess = 1;
    
    /**
     * When the errors are not defined or exceptions are not caught specifically then this
     * value is returned.
     */
    public static final int kUnknown_Error = -1;
    
	//public Hashtable mCrops; // holds all crop objects
	
    /**
     * If the file is missing or doesn't exist in that path then this value is returned.
     */    
	public static final int
		kFile_NotFound = -2
		;
	
    /**
     * The default constructor that instantiates the various data structures to 
     * store the operation related meta data.
     */ 
	public OperationObject()
	{
		mActions = new Vector <Action>();
		mCropPresent = false; //no crop as default
		mObjectName = XMLConstants.soperation;
		mOperationName = XMLConstants.sNoName;
		mActionIdVec = new Vector();
	}
	
    
    /**
     * Single argument constructor that instantiates the various data structures to 
     * store the operation related meta data.
     * @param pObjectName The name of the object which is being created.
     */    
	public OperationObject(String pObjectName)
	{
		mActions = new Vector<Action>();
		mObjectName = pObjectName;
		mCropPresent = false; //no crop as default
		mActionIdVec = new Vector();
	}
	
    /**
     * Two argument constructor that instantiates the various data structures to 
     * store the operation related meta data.
     * @param pObjectName The name of the object which is being created.
     * @param pOperationName The name of the operation for which this operation 
     * object is being created.
     */    
	public OperationObject(String pObjectName, String pOperationName)
	{
		mActions = new Vector<Action>();
		mObjectName = pObjectName;
		mOperationName = pOperationName;
		mCropPresent = false; //no crop as default
		mActionIdVec = new Vector();
	}
	
    /**
     * Two argument constructor that instantiates the various data structures to 
     * store the operation related meta data.
     * @param pActions The vector of action objects that holds this operation object.
     * @param pObjectName The name of the object which is being created.
     */    
	public OperationObject(Vector pActions, String pObjectName)
	{
		mActions = pActions;
		mObjectName = pObjectName;
		mCropPresent = false; //no crop as default
		mActionIdVec = new Vector();
	}
	
    /**
     *  Sets the operation name of this operation object to the value passed as argument.
     * @param pOperationName The new name to be set as operation name.
     */    
	public void setOperationName(String pOperationName)
	{
		mOperationName = pOperationName;
	}
	
    /**
     * This method add the to the existing vector of actions for an operation. If its
     * a planting operation then a crop is present for that operation.
     * @param pAction The action object to be added.
     */    
	public void addAction(Action pAction)
	{
		if(mActions == null)
			mActions = new Vector<Action>();
			
		mActions.add(pAction);
		mActionIdVec.add(pAction.getIdentity());	
		
		if(pAction.isCrop()) //returns true if its a planting action (identity = G:03). The next action contains all param values
			mCropPresent = true; 
		
	}
	
    /**
     * Fetches the action object from the vector sitting at index pIndex.
     * @param pIndex The index of the object in the vector.
     * @return The actin object being seeked for data retrieval or updation.
     */    
	public Action getAction(int pIndex)
	{
        //System.out.println("OperationObject : getAction() :pIndex Value is : =========== : " + pIndex );
		return (Action)mActions.get(pIndex);
	}
	
    /**
     * Fetches the object that stores all the actions for an operation or a crop.
     * @return A vector object that stores all the action objects for the operation
     * being reffered. 
     */    
	public Vector<Action> getAllActions()
	{
		return mActions;
	}
	
	/**
     * Method unused presently.
     * Fetches the operation action. There is only one operation action in any operation object. 
	 * All other actions are process or group actions
	 * @return The sole action object associated with the operation.
     */    
	public Action getOperationAction()
	{
		for( Iterator it = mActions.iterator(); it.hasNext(); )
		{
			Action action = (Action)it.next();
			if(action.isOperation() == true)
				return action;
		}
		return null;
	}
	
    /**
     * Fetches the name of the current object .. operation/crop
     * @return The object name.
     */    
	public String getObjectName()
	{
		return mObjectName;
	}
	
    /**
     * Fetches the name of the current operation object being reffered
     * @return The operation name.
     */    
	public String getOperationName()
	{
		return mOperationName;
	}
	
    /**
     * Fetches the datastructure that stores all the identity objects.
     * @return The vector that sores these identities associated with the
     * operation object.
     */    
	public Vector getAllIds()
	{
		return mActionIdVec;
	}
	
    /**
     * This method initializes the data or helps to acquire the required data for
     * initializing the various class level variables.
     * @param pNode The node whose data needs to be initialized.
     */    
	public void initialize(Node pNode)
	{
		DocumentTraversal traversable;

		if(pNode.getOwnerDocument() == null)
		{
			traversable = (DocumentTraversal)pNode;
		}
		else
			traversable = (DocumentTraversal)pNode.getOwnerDocument();

		TreeWalker walker = traversable.createTreeWalker(pNode, NodeFilter.SHOW_ALL, null, false);
		
		String nodeName = pNode.getNodeName();
		
		if(nodeName.equals(XMLConstants.soperationDB)) //it has to be ... just checking for confirmation
		{
			Node nodeChild = walker.firstChild(); // the node is one of the action (operationdefn) or paramdefn (crop_defn) from 
			while(nodeChild != null)
			{
				String nodeChildName = nodeChild.getNodeName();
				if(nodeChildName.equals(XMLConstants.soperationname))
				{
					String operationName = XMLDoc.getTextData(nodeChild);
					//String operationName = walker.firstChild().getNodeValue();
					//walker.parentNode(); //move walker to the operationName level
					
					setOperationName(operationName);
				}
				else if(nodeChildName.equals(XMLConstants.sactionvalue))
				{
					Action action = new Action();
					action.initialize(nodeChild);
					if(action.isCrop()){
                                            //returns true if its a planting action (identity = G:03). 
                                            //The next action contains all param values
                                             mCropPresent = true; 	
                                        }

					addAction(action);
					
				}
				nodeChild = walker.nextSibling();
			}
		}//end if nodeName.equals("operationDB")
	
	}
	
    /**
     * Tells whether its a planting operation which will have a crop.
     * @return True if the crop is present forhtat operation else false.
     */    
	public boolean hasCrop()
	{
		return mCropPresent;
	}
	
    /**
     * The method that fetches the name of the crop associated with the operation
     * or operation object.
     * @return The value of the crop name.
     */    
	public String getCropName()
	{
		
		for(int i = 0; i < mActionIdVec.size(); i++)
		{
			Action action = (Action)mActions.get(i);
			
			if(action.isCrop())
			{
				String cropName = (String)( (Parameter) action.getParameter(XMLConstants.sgcropname) ).getValue();
				
				return cropName;
				
			}
		}
		return null;		
	}
	
	
	/** 
     * This function is called when a new crop is inserted. then the action G:03 needs to be changed
	 * @param pCrop The new crop that is to be assigned.
     */    
	public void changeCrop(CropObject pCrop)
	{
		/* Search for the action G:03, change the crop name
		 * The Action after action G03 would be the one with all the parameter values which are needed to be changed
		 * It's not good to depned upon the order of action, but presently the data in operation and management files are 
		 * in this manner. Theres no other way to identiy the crop with its action and parameter values
		 */
		boolean ActionIsCropData = false; // to check if next action's parameters needs to be changed
		
		for(int i = 0; i < mActionIdVec.size(); i++)
		{
			Action cropAction = (Action)mActions.get(i);
			if(cropAction.isCrop())
			{
                                cropAction.changeParameterValue(XMLConstants.sgcropname, pCrop.getCropName()); // parameter to be changed is crop name
				// Change all the parameter values of next action
				Action processAction = (Action)mActions.get(i + 1);
				processAction.changeAllParameterValues(pCrop);
				return;
			}
		}
	}
		
	/** 
     * This function constructs a cropObjct from the crop values (Action G:03 and P50 or P51)
	 * Can call this function, when the crop values has to be copied from one OperationObject
     * to other or is needed for cross reference.
     * @return The orop object associated with an opration object.
     */    
	public CropObject getCrop()
	{
		CropObject cropObj = new CropObject();
		
		String cropName = getCropName();
		cropObj.setCropName(cropName);
				
		/* Copy all the parameter values from P50 /P51 to the new CropObject
		 */
		for(int i = 0; i < mActionIdVec.size(); i++)
		{	
			Action action = (Action)mActions.get(i);
			if(action.isCrop())
			{
				// get all the parameter values of next action which can be P510 or P51
				Action processAction = (Action)mActions.get(i + 1);
				
				cropObj.setCropParameters((Action)processAction);
				
				return cropObj;
			}
		}
		
		return null;
	}
	
        public String[] getValues(String pParamName, int pUnit){
            if(pParamName.equals(XMLConstants.soperationname)){
                	return new String[]{mOperationName};
            }
		
            List <String> temp = new ArrayList<String> ();
            
            //Loop over each action added the value of the parameter if it exists
            for (Action action : mActions){
                Parameter parameter = action.getParameter(pParamName);
                if (parameter != null){
                    String value = parameter.get(XMLConstants.svalue);
                    
                    //Convert units if needed.
                    if (value != null && pUnit == XMLConstants.kAlternateUnit){
                        ActionMeta actionMeta = ConfigData.getObjectMeta(XMLConstants.soperation).getActionMeta(action.getIdentity());	
			if(actionMeta != null){	
                            ParameterMeta paramMeta = actionMeta.getParameterMeta(pParamName);				
                            value = paramMeta.convertToAlternateUnits(value);		                                
			}                       
                    }
                    
                    //Add the value to the temp list
                    temp.add(value);
                }                                
            }
            
            //return as an array of Strings
            return temp.toArray(new String[temp.size()]);            
        }
        
        public void setValues(String pParamName, String[] values, int pUnit){
            //Loop over each action set the value of the parameter if it exists
            int i = 0;            
            for (Action action : mActions){
                Parameter parameter = action.getParameter(pParamName);
                if (parameter != null){                    
                    String value = values[i];
                    //Next value
                    i++;
                                        
                        ActionMeta actionMeta = ConfigData.getObjectMeta(XMLConstants.soperation).getActionMeta(action.getIdentity());	
			if(actionMeta != null){	
                            ParameterMeta paramMeta = actionMeta.getParameterMeta(pParamName);				
                            //Convert units if needed.
                            if (value != null && pUnit == XMLConstants.kAlternateUnit){
                                value = paramMeta.convertToPrimaryUnits(value);	                                    
                            }
                            value = paramMeta.convertToProperType(value);                            
			}                       
                        parameter.setValue(value);                        
                        
                        if(i == values.length){
                            break;
                        }
                }                   
            }
        }

    /**
     * Fetches the value of an operation parameter with name pParamName.
     * @param pParamName The name of the parameter whose value is being seeked.
     * @return The string value being returned.
     */    
	public String getValue(String pParamName)
	{
		if(pParamName.equals(XMLConstants.soperationname))
			return mOperationName;
		
		////System.out.println("OperationObject" + "Trying to get value for col " + pColName);  	
		/* If pColName is not operation name, then its one of the parameters in one of the Actions
		 * Search all the actions until you get the first parameter with name = pColName
		 */
		for( Iterator it = mActions.iterator(); it.hasNext(); )
		{
			Action action = (Action)it.next();
			String colValue = action.get(pParamName);
			if(colValue != null)
				return colValue;
		}
		return null;
	}
	
	
    /**
     * Fetches the value of an operation parameter with name pParamName.
     * @param pParamName The name of the parameter whose value is being seeked.
     * @param pUnit The units in which the parameter is supposed to hold its value.
     * @return The string value being returned.
     */    
	public String getValue(String pParamName, int pUnit)
	{
		if(pParamName.equals(XMLConstants.soperationname))
			return mOperationName;
		
		////System.out.println("OperationObject" + "Trying to get value for col " + pColName);  	
		/* If pColName is not operation name, then its one of the parameters in one of the Actions
		 * Search all the actions until you get the first parameterwith name = pColName
		 */
		String returnValue = null;
		Identity actionId = null;
		for( Iterator it = mActions.iterator(); it.hasNext(); )
		{
			Action action = (Action)it.next();
			String colValue = action.get(pParamName);
			if(colValue != null)
			{	
				returnValue = colValue;
				actionId = action.getIdentity();
				break;
			}	
		}
		
		if( (returnValue != null ) && (pUnit == XMLConstants.kAlternateUnit) )  // i.e conversion is required
		{
			ActionMeta actionMeta = ConfigData.getObjectMeta(XMLConstants.soperation).getActionMeta(actionId);	
			if(actionMeta != null)
			{	
				ParameterMeta paramMeta = actionMeta.getParameterMeta(pParamName);
				
				returnValue = paramMeta.convertToAlternateUnits(returnValue);
			
			}
		}
		return returnValue;
	}
	

    /**
     * This method sets the value of the operation object's parameter for a specific
     * action object to values passed as argument
     * @param pParamName The name of the parameter whose value is to be changed 
     * private set to a new value.
     * @param pNewValue Tne new value to be assigned.
     * @param pUnit The units in which the new value will be stored.
     */     
    @Override
	public void setValue(String pParamName, String pNewValue, int pUnit)
	{	
		Identity actionId = null;
		Action action = null;
		String oldValue = null;
		
		for( Iterator it = mActions.iterator(); it.hasNext(); )
		{
			action = (Action)it.next();
			oldValue = action.get(pParamName);
			if(oldValue != null)
			{	
				/* First, get to know which action is it */
				actionId = action.getIdentity();
				break;
			}	
		}
		////System.out.println("OperationObject:setValue():pNewValue:"+pNewValue);
		String valueToSet = pNewValue;
		if(actionId != null)
		{
			Parameter parameter = action.getParameter(pParamName);
			oldValue = parameter.get(XMLConstants.svalue);
			
			ActionMeta actionMeta = ConfigData.getObjectMeta(XMLConstants.soperation).getActionMeta(actionId);	
			if(actionMeta != null)
			{	
				ParameterMeta paramMeta = actionMeta.getParameterMeta(pParamName);
				if(paramMeta != null)
				{
					/* Values are always stored in Primary unit, convert it if displayed values are in Alternate unit */
					if(pUnit == XMLConstants.kAlternateUnit)
					{
						valueToSet = paramMeta.convertToPrimaryUnits(pNewValue);
						
					}		
				
					if( !(oldValue.equals(valueToSet)) )
					{
						/* Values should be written out in proper type i,e if user types in 5.00 for an integer value, 
					 	 * then it should be converted to 5 before putting the into the parameter
					 	 */
						String properTypeValue = valueToSet;
						properTypeValue = paramMeta.convertToProperType(properTypeValue);
					
						////System.out.println("OprnObj:setValue() " + "setting modified value " + pNewValue + " to " + valueToSet);	
						////System.out.println("OperationObject:setValue():pNewValue:"+pNewValue+"  properTypeValue:"+properTypeValue+"  valueToSet:"+valueToSet);
						parameter.setValue(properTypeValue);
						return;
					}
				} // if(parameterMeta != null)
			}
			else
			{				
                LOGGER.error("Could not find the actionMeta or parameterMeta. Setting new value anyway");

				parameter.setValue(pNewValue);
				return;		
			}
		}
		else{
			//LOGGER.error("Could not find the action to which this parameter belongs. Discarding new value");
        }
		
	}
			
	
    /**
     * This method checks for parameter consistency and validity of the each action
     * based on its parameters' validity to XML's well formedness etc. 
     * @return Vector that holds all the invalid and inconsistent actions & parameters.
     */    
	public Vector checkParameterConsistency()
	{
		Vector invalidActions = null;
		
		for (Iterator it = mActions.iterator(); it.hasNext(); )
         {
			Action action = (Action) it.next();
            Vector invalidParams = action.checkParameterConsistency();
			
            if(invalidParams != null)
			{
				if(invalidActions == null)
                	invalidActions = new Vector();
			
				invalidActions.add(action);
			}
		}
	
		return invalidActions;
	}
	
	/** 
     * Fetches an operationDB node.
	 * @param doc The document from where the node data or the node object is being retrieved. 
     * @return THe node object that holds or will hold the re-formatted data.
     */    
	public Node getNode(Document doc)
	{
		/**
         * Only this class knows the structure of an operationDB node and so both reading from a (operaitonDB)Node and creating
		 * new node are done in this class
		 */
        
		Node operationDBNode = doc.createElement(XMLConstants.soperationDB);
		
		setNodeContents(operationDBNode, doc);
		
		return operationDBNode;
	}
	
	/* sets a given operationDBNode with the presnet Object contents
	 */
	private void setNodeContents(Node pNode, Document pDoc)
	{
		Node operationNameNode = pDoc.createElement(XMLConstants.soperationname);                
		XMLDoc.setTextData(operationNameNode, mOperationName, pDoc);
	
		pNode.appendChild(operationNameNode);
		
		for(int i = 0; i < mActionIdVec.size(); i++)
		{
			Action action = (Action)mActions.get(i);
			Node actionvalueNode = action.getNode(pDoc);
			
			pNode.appendChild(actionvalueNode);
			
		}
		/** Old implementation. Replaced by above code on May 30, 2003
		Enumeration enum = mActions.elements();
		while(enum.hasMoreElements())
		{
			Action action = (Action)enum.nextElement();
			Node actionvalueNode = action.getNode(doc);
			
			operationDBNode.appendChild(actionvalueNode);
		} */
				
	}
        
        private void setNodeContents(Node pNode, Document pDoc, String newFileName)
	{
		Node operationNameNode = pDoc.createElement(XMLConstants.soperationname);
                
		if( XMLConstants.soperationname.equals(operationNameNode.getNodeName()) && ( !operationNameNode.getNodeName().equals( newFileName )) ){
                   XMLDoc.setTextData(operationNameNode, newFileName, pDoc);
                }
	
		pNode.appendChild(operationNameNode);
		
		for(int i = 0; i < mActionIdVec.size(); i++)
		{
			Action action = (Action)mActions.get(i);
			Node actionvalueNode = action.getNode(pDoc);
			pNode.appendChild(actionvalueNode);
			
		}
						
	}
	
	
        /**
         * Makes a copy of the current object.
         * @return The new copy of the existing object.
         */        
	public Object clone()
	{
		//try
		{
			OperationObject copy = (OperationObject)super.clone();
			
			//System.err.println("OperationObject:clone" + " Cloning "+ mOperationName);
			
			copy.mOperationName = mOperationName;
			copy.mCropPresent = mCropPresent;
			
			copy.mActions = new Vector();
        		for(int i = 0; i < mActions.size(); i++)
			{
				Action action = (Action)mActions.get(i);
				Action actionCopy = (Action)action.clone();
				copy.mActions.add(actionCopy);
			}
			
			// Now clone the mActionIdVec also
			// Direct cloning of a vector will only give a shallow copy
			copy.mActionIdVec = new Vector();
			for(int i = 0; i < mActionIdVec.size(); i++)
			{
				Identity id = (Identity)mActionIdVec.get(i);
				Identity idCopy  = (Identity)id.clone();
				copy.mActionIdVec.add(idCopy);
			}
			
			return copy;
		}
		/*catch(CloneNotSupportedException e) // raises the error 'Exception never thrown' in the preceeding body of try
		{
			System.err.println("OperationObject:Clone();" + "Clone Not Supported!");
		}
		return null; */
	}
	
	/** Read new operation xml file (*.oprn). Note that initialize() is called directly when reading from management
	 * files. This function is called to read from operation files while adding new operations.
	 * @param pFileName The XML file from which we get the data in a particular format.
         * @return positive integer if success else negative error code
	 */ 
	public int readXMLFile(String pFileName)
	{
		DataObject dataObject = null;
		Node root, node;
		Document doc;
		RowInfo row;		
		doc = XMLDoc.getDocument(pFileName);
		if(doc == null) 
			return kUnknown_Error; 
			
		doc.normalize();
		initialize(doc.getDocumentElement());

                //handle the fix up of the ofuel from older versions
                Action action = getOperationAction();
                if(action.getParameter("ofuel") == null){
                    if(action.getIdentity().id == 3 || action.getIdentity().id==4){
                        action.addParameter(new Parameter("ofuel", " "));
                    }
                }
	
                if (checkForRequiredGroups(true)){
                    return kUnknown_Error;
                }
                else{
                    return kSuccess;		
                }		
	}
	

      /**
     * Writes a new operation xml file. This function is called when user selcts to save a new operaition from Operation_Dlg.
     * @param pFileName The name of the file to which the data will be written.
     * @param hasCrop - true if the operation has a crop
     * @param withCrop - true if this new saved operation should also retain that crop
     * @return Returns a positive integer if the file was written successfully else
     * negative.
     */    
	public int writeXMLFile(String pFileName, boolean hasCrop, boolean withCrop)
	{
		boolean writeFile = true;
                //If the operation is to be saved with no crop, then defer 
                // writing the operation file - Neha.
                if(hasCrop == true && withCrop == false){
                    writeFile = false;
                }
                String newOperFileName = null;
                 if(!pFileName.endsWith(".oprn")){
                    pFileName = pFileName+".oprn";
                }
                newOperFileName = pFileName.substring(pFileName.lastIndexOf("\\") + 1, pFileName.lastIndexOf(".") );
                if(newOperFileName == null){
                        newOperFileName = pFileName.substring(pFileName.lastIndexOf("/") + 1, pFileName.lastIndexOf(".") );
                 }
                    //System.out.println("OperationObject : writeXMLFile : Filename OUTSIDE is - - -  " + newOperFileName );
            
                Node root;
                String nonEscapingElements[] = {XMLConstants.soperationname, XMLConstants.sname};
		
		try{
			
			/* createDocument function cretes a new Document with appropriate Doctype and stylesheet attached */
			Document operationDoc = XMLDoc.createDocument(XMLConstants.soperation);  
			if(operationDoc == null)
			{
				System.err.println("OprnObject:writerXMLFile(): "+ "operationDoc is null");
				return kUnknown_Error;
			}
			
			/* Note: 
			 */
			root = operationDoc.getDocumentElement();
			setNodeContents(root, operationDoc, newOperFileName);
                        
			OutputFormat format = new OutputFormat(operationDoc);
			format.setIndenting(true);
			format.setIndent(XMLConstants.INDENT);
			format.setEncoding(XMLConstants.sEncoding);
			format.setNonEscapingElements(nonEscapingElements);
			format.setMediaType(XMLConstants.sMediaType);
			format.setVersion(XMLConstants.sVersion);
                         
                        if(writeFile == true){
                           //BufferedWriter bw = new BufferedWriter(new FileWriter(pFileName));
                            FileWriter fw = new FileWriter(pFileName);

                            XMLSerializer serializer = new XMLSerializer(fw,format);
                            serializer.serialize(operationDoc);
                        }
                        /* If the Operation is to be saved with No Crop,then create an OperationObject 
                         * that has the same properties as this operation, and then change its crop
                         * to no_crop.crop and then write an XML file with the given file name - neha
                         */
                        if(hasCrop == true && withCrop == false){
                            OperationObject opObject = new OperationObject();
                            opObject.initialize(root);
                            String fullCropName = new File("mcrew_cfg", XMLConstants.sno_cropFileName).getAbsolutePath();
                            Node cropNode = SkelImportPanel.loadXMLCropFile(fullCropName);
                            CropObject crop = new CropObject();
                            crop.initialize(cropNode);
                            opObject.changeCrop(crop);
                            opObject.writeXMLFile(pFileName, false, false);
                        }
		}
		catch(DOMException e)
		{
			System.err.println("DomException");
		}
		catch(java.io.IOException e)
		{
			e.printStackTrace();
		}
		return kSuccess;
	}

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final OperationObject other = (OperationObject) obj;
        if (this.mActions != other.mActions && (this.mActions == null || !this.mActions.equals(other.mActions))) {
            return false;
        }
        if (this.mOperationName != other.mOperationName && (this.mOperationName == null || !this.mOperationName.equals(other.mOperationName))) {
            return false;
        }
        if (this.mActionIdVec != other.mActionIdVec && (this.mActionIdVec == null || !this.mActionIdVec.equals(other.mActionIdVec))) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 37 * hash + (this.mOperationName != null ? this.mOperationName.hashCode() : 0);
        return hash;
    }
        
        
        
    /**
     * Checks the loaded operation processes for required groups.
     * @param showMessage Indicates if a WepsMessageDialog box should be shown with any errors
     * found.  The dialog is not displayed if there are no errors.
     * @return True when there were errors.  Returns false when there are no missing
     * groups.
     */
        public boolean checkForRequiredGroups(boolean showMessage){            
            WepsMessageLog log = new WepsMessageLog ();   
            boolean firstError = true;
            for (Object o : getAllActions()){
                Action action = (Action) o;
                Identity id = action.getIdentity();
                ActionMeta actionMeta = ConfigData.getObjectMeta(XMLConstants.soperation).getActionMeta(id);            
                Vector applicableGroupIds = actionMeta.getGroupId();
                String applicableGroupList = "";
                
                
                        
                if (applicableGroupIds != null){
                    for (Object q : applicableGroupIds){
                        Identity qId = (Identity) q;
                        if (applicableGroupList.length() == 0){
                            applicableGroupList = "Groups: [" + qId.toString() + "]";
                        }
                        else{
                            applicableGroupList = applicableGroupList + ", ["+ qId.toString()+ "]";
                        }                    
                    }
 
                    //This action requires a group                    
                    
                    //Start at the current action and work our way up the list.
                    boolean foundGroup = false;
                    for (int actionIndex = getAllActions().indexOf(action); actionIndex >= 0 && !foundGroup; actionIndex--){
                        Action testAction = this.getAction(actionIndex);
                        Identity testId = testAction.getIdentity();
                        for (Object p : applicableGroupIds){
                            Identity testGroup = (Identity) p;
                            if (testGroup.equals(testId)){
                                foundGroup = true;
                                break;
                            }                            
                        }                        
                    }
                    
                    if (!foundGroup){
                        if (firstError){
                            log.logMessage(WepsMessage.errorMessage("Errors were found in the operation file.\n"));
                            firstError = false;
                        }
                        log.logMessage(WepsMessage.errorMessage(action.getActionName() + " [" + id.toString() + "] requires a group. " + applicableGroupList));                        
                    }
                }                                
            }
            
            if (log.hasMessage()){
                if (showMessage){
                    //Show a warning message.                   
                    WepsMessageDialog.showMessageList(null, "Missing Groups", ConfigData.getConfigDir(), log.getMessages());
                }                
                return true;
            }              
            else return false;
            
        }
}
