package JavaAgent.resource;

import JavaAgent.agent.Agent;

import java.util.Hashtable;
import java.util.Enumeration;

/**
 * Abstract superclass for all resource objects owned by an Agent which must
 * be handled as condition variables (i.e. access to the object must be
 * synchronized). Objects can be added, removed and queried for existence. 
 * Storage is modeled by a Hashtable where the keys are identifiers and
 * the objects are the actual resource elements being stored. Object locations
 * are stored in a separate Hashtable, the location for code objects is 
 * represented by a FileLocation object. A NullResource object is used as a 
 * temporary marker for nonexistant objects.<p>
 *
 * <hr>
 * Copyright (c) 1995, H. Robert Frost, Stanford University.
 * All rights reserved.<p>
 * Copyright (c) 1996, H. Robert Frost, Enterprise Integration Technologies,
 * Inc. All rights reserved.<p>
 *
 * RESTRICTED RIGHTS LEGEND: Use, duplication or disclosure by the 
 * Government is subject to restrictions as set forth in 
 * subparagraph(c)(1)(ii) of the Rights in Technical Data and Computer
 * Software clause at DFARS 252.227-7013 and in similar clauses in the
 * FAR and NASA FAR supplement.<p>
 * 
 * This software is bound by the terms and conditions listed in the
 * attached <a href="../LICENSE">LICENSE file</A>.
 * <hr>
 * @author <A HREF="http://cdr.stanford.edu/html/people/frost-bio.html"> Rob Frost</A>
 * @version 0.3 5/21/96 for Java(tm) Language Version 1.0.2
 */

public class Resource {

  /**
   * Specifies that no attempt should be made to retrieve the element.
   */
  public static final int NO_RETRIEVAL = 0;

  /* keys are identifiers, objects are the resources */
  protected Hashtable storage;
  /* keys are identifiers, objects are resource locations */
  protected Hashtable locations;
  /* Agent which owns this resource */
  protected Agent parent;

  /**
   * Constructor for the resource, initializes the storage space.
   *
   * @param agent The Agent which owns the resource.
   */

  public Resource(Agent agent){
    storage = new Hashtable();
    locations = new Hashtable();
    parent = agent;
  }

  /**
   * Provides persistent storage of the Resource elements. This storage
   * could be through a local file or a remote process.
   * This method must be overridden and the uncache() method must also be
   * implemented.
   * @return Object representing the storage location.
   */

   public Object cache() throws ResourceException {
     throw new ResourceException("Cache not implemented");
   }

  /**
   * Adds elements to the Resource from persistent storage.
   * This method must be overridden.
   * @return location Object representing the storage location.
   */

   public void uncache(Object location) throws ResourceException {
     throw new ResourceException("Uncache not implemented");
   }

  /**
   * Returns the number of elements.
   * @return Number of elements.
   */
  
  public int size(){
    clean();
    return storage.size();
  }

  /**
   * @return Enumeration of the element names.
   */

  public Enumeration getElements(){
    clean();
    return storage.keys();
  }

  /**
   * Removes all NullResource objects from the storage Hashtable.
   */
  
  protected void clean(){
    Enumeration identifiers = storage.keys();
    while(identifiers.hasMoreElements()){
      Object identifier = identifiers.nextElement();
      Object item = storage.get(identifier);
      if(item.getClass().getName().equals("Agent.NullResource")){
	storage.remove(identifier);
      }
    }
  }

  /**
   * Checks to see if a given identifier already exists.
   *
   * @param indentifier Identifier whose existence is being checked.
   * @return true if the identifier exists, false otherwise.
   */

  public synchronized boolean hasElement(Object identifier){
    boolean answer = false;
    if(storage.containsKey(identifier)){
      Object element = (Object)storage.get(identifier);
      if(element.getClass().getName().equals("Agent.NullResource")) {      
	storage.remove(identifier);
	answer = false;
      }
      answer = true;
    }
    return answer;
  }

  /**
   * Synchronized method which adds a new element Object associated with an 
   * identifier Object. Calls the notify() method to inform threads which 
   * might be waiting on a getElement() method.
   * Depending on the value of the replace boolean, the element associated
   * with a pre-existing identifier is either replaced or left unchanged.
   *
   * @param identifier Object identifier for the element.
   * @param element Object associated with the identifier.
   * @param replace Should the element of a pre-existing identifier be
   * replaced?
   */

  public synchronized void addElement(Object identifier, Object element,
				      boolean replace){
    if(!storage.containsKey(identifier)){
      storage.put(identifier,element);
      parent.resourceChanged(this);
      notifyAll();
    } else {
      if(replace){
	storage.put(identifier,element);
	parent.resourceChanged(this);
      }
    }
  }

  /**
   * Method which adds a location Object associated with an 
   * identifier Object.
   * Depending on the value of the replace boolean, the element associated
   * with a pre-existing identifier is either replaced or left unchanged.
   * 
   * For resources which are pieces of code the location is represented by
   * a FileLocation object.
   *
   * @param identifier Object identifier for the element.
   * @param location Location associated with the identifier.
   * @param replace Should the location of a pre-existing identifier be
   * replaced?
   */

  public void addLocation(Object identifier, Object location,
			  boolean replace){
    if(!locations.containsKey(identifier)){
      locations.put(identifier,location);
    } else {
      if(replace){
	locations.put(identifier,location);
      }
    }
  }

  /**
   * Synchronized method which returns the Object identified by the input
   * identifier. If an object associated with the identifier does not exist
   * then the getMissingAction() method is called. This method currently
   * throws a ResourceException.
   *
   * @param identifier Object identifier for the element.
   * @param retrieve_option Specifies action to take if the element is
   * not currently present.
   * @param receiver Agent to whom retrieve requests should be sent, null
   * if default should be used.
   * @return Object associated with the identifier.
   */

  public synchronized Object getElement(Object identifier, int retrieve_option,
					String receiver)
    throws ResourceException{
    Object answer = null;
    
    if(storage.containsKey(identifier)){
      answer = (Object)storage.get(identifier);
      if(answer.getClass().getName().equals("Agent.NullResource")) {
	/* had a null stored */
	storage.remove(identifier);
	if(retrieve_option > NO_RETRIEVAL){
	  answer = getMissingAction(identifier,retrieve_option,receiver);
	} else {
	  throw new ResourceException(identifier + "not found");
	}
      }
    } else if(retrieve_option > NO_RETRIEVAL) {
      answer = getMissingAction(identifier,retrieve_option,receiver);
    } else {
      throw new ResourceException(identifier + "not found");
    }

    return answer;
  }
  
  /**
   * Automatically uses the default receiver for retrieve messages.
   * @param identifier Object identifier for the element.
   * @param retrieve_option Specifies action to take if the element is
   * not currently present.
   * @return Object associated with the identifier.
   */
  
  public synchronized Object getElement(Object identifier, int retrieve_option)
    throws ResourceException{
   return getElement(identifier,retrieve_option,null);
  }

  /**
   * Action to take when there is an attempt to retrieve an element
   * associated with a non-existent identifier. Currently throws a 
   * ResourceException. Override to do something else.
   *
   * @param identifier Object identifier for the element.
   * @param retrieve_option Specifies action to take if the element is
   * not currently present.
   * @param receiver Agent to whom retrieve requests should be sent, null
   * if default should be used.
   * @return Object to be returned.
   */

  public Object getMissingAction(Object identifier,int retrieve_option,
				 String receiver)
    throws ResourceException {
      throw (new ResourceException("Identifier does not exist"));
    }

  /**
   * Method which returns the location associated with the input
   * identifier. Returns null if the identifier is not contained.
   *
   * @param identifier Object identifier for the location
   * @return Location Object associated with the identifier.
   */

  public Object getLocation(Object identifier){
    Object answer = null;
    
    if(locations.containsKey(identifier)){
      answer = (Object)locations.get(identifier);
    }

    return answer;
  }

  /**
   * Synchronized method which is called to remove the element associated
   * with a given identifier. If the identifier does not exist, the
   * removeMissingAction() method is called (currently does nothing).
   *
   * @param identifier Identifier of the Object to remove.
   */

  public synchronized void removeElement(Object identifier){
    if(storage.containsKey(identifier)){
      storage.remove(identifier);
      parent.resourceChanged(this);
    } else {
      removeMissingAction(identifier);
    }
  }
  
  /**
   * Action to be taken when there is an attempt to remove the element
   * associated with a non-existant identifier. Must be overridden to do
   * something. 
   *
   * @param identifier Identifier of the non-existent object.
   */

  public void removeMissingAction(Object identifier){}

  /**
   * Removes the location associated
   * with a given identifier. Does nothing if the identifier does not exist.
   *
   * @param identifier Identifier of the location to remove.
   */

  public void removeLocation(Object identifier){
    if(locations.containsKey(identifier)){
      locations.remove(identifier);
    }
  }

}
  

