package JavaAgent.resource.cdl;


import java.io.*;
import java.util.*;
import java.lang.reflect.*;
import java.text.ParseException;

import JavaAgent.agent.Agent;
import JavaAgent.resource.Language;
import JavaAgent.resource.RetrievalResource;
import JavaAgent.resource.fopl.*;

/** 
** A CDLDescription is a generic description of some capability. The language
** in which capabilities are described is <a href = 
** "http://www.dai.ed.ac.uk/students/gw/phd/capalang.html" >CDL</a>.
*/

public class CDLDescription implements Parsable {

  /**
  ** This constructor is for an empty, uninitialized CDLDescription. All
  ** fields still need to be defined before it can be used!
  */
  private CDLDescription() {
  }

  /**
  ** This constructor for a CDLDescription takes a String and parses it. The
  ** given String is tokenized using a StreamTokenizer in which the characters
  ** ':' and '?' are treated as word characters.
  ** @param advertiser the name of the agent advertising the capability
  ** @param CDstr the String representing the CDLDescription
  ** @param receiver the broker which received the capability description
  ** @exception ParseException An exception will occur if a syntactical or 
  ** other type of error is discovered.
  ** @see StreamTokenizer
  */
  public CDLDescription(String advertiser, String CDstr, Agent receiver, 
      Hashtable ontology) throws ParseException {
    cAgent = advertiser;
    StreamTokenizer st = new StreamTokenizer(new StringReader(CDstr));
    st.wordChars(':', ':');
    st.wordChars('?', '?');
    try {
      st.nextToken();

      // expect an open bracket:
      parseToken(st, '(');

      // expect "capability" or "task":
      if (parseWord(st, "capability", false))
	isCapability = true;
      else if (parseWord(st, "task", false))
	isCapability = false;
      else pErr(st, "expecting \"capability\" or \"task\"!");

      // expect the optional action spec:
      if (parseWord(st, ":action", false))
	actionId = parseWord(st, "Action identifier expected!");
      else
	actionId = null;

      // expect the optional action type:
      if (parseWord(st, ":isa", false)) {
	superAct = (CDLDescription)ontology.
            get(parseWord(st, "Action identifier expected!"));
      }
      else
	superAct = null;

      // expect the optional property list:
      if (parseWord(st, ":properties", false)) {
	properties = parseProperties(st);
      }
      else
	properties = null;

      // expect the state language specification:
      if (parseWord(st, ":state-language", true)) {
	try {
	  stateLangId = parseWord(st, 
              "state language resource identifier expected.");
	  stateLangClass = (Class)receiver.getResource("language").
              getElement(stateLangId, RetrievalResource.SEND_MSG, advertiser);
	} catch (JavaAgent.resource.ResourceException re) {
	  cErr(st, "Resource for state language not found!");
	}
      }

      // use new Variables except for inherited Variables:
      Variable.newVars();
      if (superAct != null) {
	Vector superVars = superAct.getVars();
	for (Enumeration ve=superVars.elements(); ve.hasMoreElements(); )
	  Variable.useVariable((Variable)ve.nextElement());
      }

      // expect the optional input specification:
      input = (parseWord(st, ":input", false)) ? 
          parseParameters(st) : null;

      // expect the optional output specification:
      output = (parseWord(st, ":output", false)) ?
	  parseParameters(st) : null;

      // expect the optional input constraints:
      inputConstrs = (parseWord(st, ":input-constraints", false)) ?
	  parseConstraints(st) : null;

      // expect the optional output constraints:
      outputConstrs = (parseWord(st, ":output-constraints", false)) ?
	  parseConstraints(st) : null;

      // expect the optional i/o constraints:
      ioConstrs = (parseWord(st, ":io-constraints", false)) ?
	  parseConstraints(st) : null;

      parseToken(st, ')');
      if (st.ttype != StreamTokenizer.TT_EOF)
	pErr(st, "unexpected text after final closing bracket.");
    } catch (IOException ioe) {
      throw new UnknownError(
          "Failed to read next token from capability string!");
    }
  }

  /**
  ** This function parses a list of properties.
  ** @param st the StreamTokenizer from which will be parsed
  ** @return a Vector of properties (Symbols)
  ** @exception ParseException An exception can occur if the syntax is
  **   not as expected.
  ** @exception IOException An exception will will be thrown if advancing
  **   on the StreamTokenizer fails.
  */
  static protected Vector parseProperties(StreamTokenizer st)
      throws IOException, ParseException {
    Vector props = new Vector(5, 3);
    parseToken(st, '(');
    while (st.ttype != ')') {
      props.addElement(Symbol.get(parseWord(st, "Property expected!")));
    }
    parseToken(st, ')');
    return props;
  }

  /**
  ** This function parses a list of parameter specifications for a
  ** capability description. Currently, these consist just of a role name
  ** and a variable enclosed in round brackets.
  ** @param st the StreamTokenizer from which the specs will be parsed
  ** @return a Vector of parameter specifications
  ** @exception ParseException An exception can occur if the syntax is
  **   not as expected.
  ** @exception IOException An exception will will be thrown if advancing
  **   on the StreamTokenizer fails.
  */
  static protected Vector parseParameters(StreamTokenizer st) 
      throws IOException, ParseException {
    Vector params = new Vector(5, 3);
    parseToken(st, '(');
    while (st.ttype == '(') {
      parseToken(st, '(');
      Symbol role = Symbol.get(parseWord(st, 
          "expecting role name for parameter!"));
      Term filler = Term.parse(parseWord(st, 
          "expecting role filler for " + role + '!'));
      params.addElement(new ParamSpec(role, filler));
      parseToken(st, ')');
    }
    parseToken(st, ')');
    return params;
  }

  /**
  ** This function parses a list of constraints. The list must be enclosed by 
  ** round brackets and so must the constraints in the list. Each constraint
  ** must be a String representing an expression that can be parsed into a
  ** member of the class stored in stateLangClass.
  ** @param st the StreamTokenizer from which the constraints will be parsed
  ** @return a Vector of constraints; elements will be members of the class 
  ** stored in stateLangClass
  ** @exception ParseException An exception can occur if the brackets are not
  **   as expected or if a ParseException occurs during the parsing of the 
  **   constraints including failure to instantiate the Class.
  ** @exception IOException An exception will will be thrown if advancing
  **   on the StreamTokenizer fails.
  */
  protected Vector parseConstraints(StreamTokenizer st) 
      throws IOException, ParseException {
    Vector constrs = new Vector(5, 3);
    parseToken(st, '(');
    while (st.ttype == '(') {
      try {
	Language nextConstr = (Language)stateLangClass.newInstance();
	nextConstr.parseString(extractExpr(st));
	constrs.addElement(nextConstr);
      } catch (Exception e) {
	cErr(st, "failed to build internal constraint representation!");
      }
    }
    parseToken(st, ')');
    return constrs;
  }

  /**
  ** This function extracts an expression from the given StreamTokenizer. The
  ** expression must be in round brackets and the brackets within the 
  ** expression must be balanced. The returned String will be this expression,
  ** but some of the spacing might have changed.
  ** @param st the StreamTokenizer from which the expression is to be read
  ** @return a String representing the extracted expression
  ** @exception ParseException An exception can occur if the StreamTokenizer
  **   runs out of tokens before the final balancing brcket has been read.
  ** @exception IOException An exception will will be thrown if advancing
  **   on the StreamTokenizer fails.
  */
  static public String extractExpr(StreamTokenizer st) 
      throws IOException, ParseException {
    int brCount = 0;
    StringBuffer expr = new StringBuffer(255);
    do {
      if (st.ttype == StreamTokenizer.TT_EOL)
	expr.append(' ');
      else if (st.ttype == StreamTokenizer.TT_NUMBER)
	expr.append(st.nval).append(' ');
      else if (st.ttype == StreamTokenizer.TT_WORD)
	expr.append(st.sval).append(' ');
      else if (st.ttype == '"')
	expr.append('\"').append(st.sval).append('\"').append(' ');
      else {
	if (st.ttype == '(')
	  brCount++;
	if (st.ttype == ')')
	  brCount--;
	expr.append((char)st.ttype);
      }
    } while ((st.nextToken() != StreamTokenizer.TT_EOF) && (brCount > 0));
    if (brCount != 0)
      pErr(st, "matching number of closing brackets expected!");
    return expr.toString();
  }

  /**
  ** This function parses a word from the given StreamTokenizer. It returns 
  ** a String that is the word that has been overread.
  ** @param st the StreamTokenizer from which to parse
  ** @param msg the error message to be displayed should a ParseException
  **   be thrown form this function
  ** @return the parsed word
  ** @exception ParseException An exception may occur if the parsed was
  **   not a word token
  ** @exception IOException An exception will will be thrown if advancing
  **   on the StreamTokenizer fails.
  */
  static protected String parseWord(StreamTokenizer st, String msg) 
      throws IOException, ParseException {
    if (st.ttype != StreamTokenizer.TT_WORD)
      pErr(st, msg);
    String w = st.sval;
    st.nextToken();
    return w;
  }

  /**
  ** This function parses a word from the given StreamTokenizer. It returns 
  ** whether the parsed token was a word and equal to the given String and 
  ** overreads the word token. However, if the given boolean was true is 
  ** true then a ParseException will be thrown if the parsed word was not 
  ** equal to the given String.
  ** @param st the StreamTokenizer from which to parse the token
  ** @param word the expected word
  ** @param throwIfNot indicates whether a ParseException should be thrown 
  **   if the parsed token is not the given word
  ** @return whether the parsed token was the given word
  ** @exception ParseException An exception may occur if the parsed word
  **   was not as expected and throwIfNot was true
  ** @exception IOException An exception will will be thrown if advancing
  **   on the StreamTokenizer fails.
  */
  static protected boolean parseWord(StreamTokenizer st, String word, 
      boolean throwIfNot) throws IOException, ParseException {
    if ((st.ttype == StreamTokenizer.TT_WORD) && (st.sval.equals(word))) {
      st.nextToken();
      return true;
    }
    if (throwIfNot)
      pErr(st, "expecting \"" + word + "\"!");
    return false;
  }

  /**
  ** This funtion parses a token from the given StreamTokenizer. If the type
  ** is the given character value then the token is overread. Otherwise a
  ** ParseException with a message that this character was expected will be 
  ** thrown.
  ** @param st the StreamTokenizer from which to parse the token
  ** @param c the character indicating the expected token type
  ** @exception IOException An exception will will be thrown if advancing
  **   on the StreamTokenizer fails.
  ** @exception ParseException An exception will be thrown if the current
  **   token in the StreamTokenizer is not the expected character.
  */
  static protected void parseToken(StreamTokenizer st, char c) 
      throws IOException, ParseException {
    if (st.ttype == c)
      st.nextToken();
    else
      pErr(st, "expecting \'" + c + "\'!");
  }

  /**
  ** This function unconditionally throws a ParseException containing
  ** the given String in its message. This function should be used to 
  ** indicate a parsing problem that is a syntactic problem.
  ** @param st the StreamTokenizer for parsing a capability description
  ** @param msg the message explaining the current problem
  ** @exception ParseException The Exception that will allways be thrown.
  */
  static private void pErr(StreamTokenizer st, String msg) 
      throws ParseException {
    throw new ParseException(
        "Parse error in CDLDescription; " + msg, st.lineno());
  }

  /**
  ** This function unconditionally throws a ParseException containing
  ** the given String in its message. This function should be used to 
  ** indicate a parsing problem that is not just a syntactic problem.
  ** @param st the StreamTokenizer for parsing a capability description
  ** @param msg the message explaining the current problem
  ** @exception ParseException The Exception that will allways be thrown.
  */
  static private void cErr(StreamTokenizer st, String msg) 
      throws ParseException {
    throw new ParseException(
        "Error in CDLDescription; " + msg, st.lineno());
  }

  /**
  ** This funcion returns a clone of this CDLDescription using the same
  ** Variables.
  */
  public Object clone() {
    CDLDescription res = new CDLDescription();
    res.cAgent = cAgent;
    res.isCapability = isCapability;
    res.stateLangId = stateLangId;
    res.stateLangClass = stateLangClass;
    res.actionId = actionId;
    res.superAct = superAct;
    res.properties = 
      (properties != null) ? (Vector)properties.clone() : null;
    res.input = 
      (input != null) ? (Vector)input.clone() : null;
    res.output = 
      (output != null) ? (Vector)output.clone() : null;
    res.inputConstrs = 
      (inputConstrs != null) ? (Vector)inputConstrs.clone() : null;
    res.outputConstrs = 
      (outputConstrs != null) ? (Vector)outputConstrs.clone() : null;
    res.ioConstrs = 
      (ioConstrs != null) ? (Vector)ioConstrs.clone() : null;
    return res;
  }
    

  /**
  ** This function tests whether this capability can perform the given
  ** task. 
  */
  public boolean subsumes(CDLDescription cdlTask)
      throws FormalismException {

    CDLInterpreter.traceln("Testing whether " + this + " subsumes " + cdlTask);

    if (superAct != null) {
      return instantiate().subsumes(cdlTask);
    }
    if (cdlTask.superAct != null) {
      return subsumes(cdlTask.instantiate());
    }

    if ((cdlTask.properties != null) && (! cdlTask.properties.isEmpty())) {
      if ((properties == null) || (properties.isEmpty()))
	return false;
      else
	if (! testProps(cdlTask.properties))
	  return false;
    }
    CDLInterpreter.traceln("Properties ok.");

    KnowledgeBase outKB = getKB(outputConstrs);
    //System.out.println("Output constraints knowledge base created ...");
    try {
      if (ioConstrs != null)
	addIOconclusions(outKB);
      CDLInterpreter.traceln("Capability output knowledge base:");
      ((FormulaSet)outKB).writeToStream(CDLInterpreter.traceStream);
      KRSentence outQuery = (KRSentence)stateLangClass.
	  getMethod("getConjunction", new Class[]{
	      Class.forName("java.util.Vector")}).
	  invoke(null, new Object[]{cdlTask.outputConstrs});
      CDLInterpreter.traceln("Task output query: " + outQuery);

      Class evalArgT[] = {
	  Class.forName("JavaAgent.resource.cdl.KRSentence"), 
	  Class.forName("java.util.Vector") };
      Vector iVars = getInputVars();
      CDLInterpreter.traceln("Input variables: " + iVars);
      Hashtable map = (Hashtable)outKB.getClass().
	  getMethod("evaluate", evalArgT).
	  invoke(outKB, new Object[]{outQuery, iVars});
      //System.out.println("Task output evaluated ...");
      if (map == null)
	return false;

      Substitution s = new Substitution();
      for (Enumeration ve=map.keys(); ve.hasMoreElements(); ) {
	Variable nv = (Variable)ve.nextElement();
	Term nt = (Term)map.get(nv);
	if ((nt != null) && (!(nt instanceof JavaAgent.resource.fopl.VarTerm)))
	  s.unify(nv, nt);
      }
      CDLInterpreter.traceln("Subsumption substitution: " + s);

      KnowledgeBase taskInKB = getKB(cdlTask.inputConstrs);
      //System.out.println("Input constraints knowledge base created ...");
      CDLInterpreter.traceln("Task input knowledge base:");
      ((FormulaSet)taskInKB).writeToStream(CDLInterpreter.traceStream);
      for (Enumeration krse=inputConstrs.elements(); krse.hasMoreElements();) {
	KRSentence inQuery = (KRSentence)krse.nextElement();
	inQuery = (KRSentence)inQuery.getClass().getMethod("clone", 
	    new Class[]{Class.forName(
            "JavaAgent.resource.fopl.Substitution")}).invoke(inQuery, 
            new Object[]{s});
	CDLInterpreter.traceln("Capability input query: " + inQuery);
	if (! ((Boolean)taskInKB.getClass().getMethod("evaluate", new Class[]{
            Class.forName("JavaAgent.resource.cdl.KRSentence")}).
	    invoke(taskInKB, new Object[]{inQuery})).booleanValue())
	  return false;
      }
      //System.out.println("Task input evaluated ...");
      if (ioConstrs == null)
	return true;
      Class[] noArgs = new Class[0];
      for (Enumeration krse=ioConstrs.elements(); krse.hasMoreElements();) {
	KRSentence ioConstr = (KRSentence)krse.nextElement();
	KRSentence concl = (KRSentence)ioConstr.getClass().
            getMethod("getConclusion", noArgs).invoke(ioConstr, noArgs);
	CDLInterpreter.traceln("Testing IOC-Conclusion: " + concl);
	if (((Boolean)outKB.getClass().getMethod("usedInProof", 
            new Class[]{Class.forName("JavaAgent.resource.cdl.KRSentence")}).
            invoke(outKB, new Object[]{concl})).booleanValue()) {
          KRSentence premise = (KRSentence)ioConstr.getClass().
              getMethod("getPremise", noArgs).invoke(ioConstr, noArgs);
	  premise = (KRSentence)premise.getClass().getMethod("clone", 
	      new Class[]{Class.forName(
              "JavaAgent.resource.fopl.Substitution")}).invoke(premise, 
              new Object[]{s});
	  CDLInterpreter.traceln("Evaluating IOC-premise: " + premise);
	  if (! ((Boolean)taskInKB.getClass().getMethod("evaluate", 
              new Class[]{Class.forName("JavaAgent.resource.cdl.KRSentence")}).
	      invoke(taskInKB, new Object[]{premise})).booleanValue())
	    return false;
        }
      }
      return true;

    } catch (Exception e) {
      throw new FormalismException(
          "Failed to test capability: " + e.getMessage());
    }
  }

  private boolean testProps(Vector props) {
    for (Enumeration pe=props.elements(); pe.hasMoreElements(); )
      if (! properties.contains(pe.nextElement()))
	return false;
    return true;
  }

  private KnowledgeBase getKB(Vector forms)
      throws FormalismException {
    Class[] noArgs = new Class[0];
    try {
      Method getKBClassMethod = 
	  stateLangClass.getMethod("getKBClass", noArgs);
      Class kbClass = (Class)getKBClassMethod.invoke(null, noArgs);
      KnowledgeBase kb = (KnowledgeBase)kbClass.newInstance();
      for (Enumeration f=forms.elements(); f.hasMoreElements(); )
	kb.assert((KRSentence)f.nextElement());
      return kb;
    } catch (Exception e) {
      throw new FormalismException(
          "Failed to construct KB: " + e.getMessage());
    }
  }

  private void addIOconclusions(KnowledgeBase outKB) throws Exception {
    Class[] noArgs = new Class[0];
    for (Enumeration f=ioConstrs.elements(); f.hasMoreElements(); ) {
      KRSentence krs = (KRSentence)f.nextElement();
      outKB.assert((KRSentence)krs.getClass().
          getMethod("getConclusion", noArgs).invoke(krs, noArgs));
    }
  }

  /**
  ** This function returns the name of the agent that has this capability.
  ** @return the agent that has this capability
  */
  public String getAgent() {
    return cAgent;
  }

  /**
  ** This function tests whether this CDLDescription is a capability
  ** description or a task description.
  ** @return <tt>true</tt> iff this is a capability description; 
  ** <tt>false</tt> iff it is a task
  */
  public boolean isCapability() {
    return isCapability;
  }

  /**
  ** This function returns the action identifier for this capability.
  ** @return the action identifier for this capability
  */
  public String getAction() {
    return actionId;
  }

  /**
  ** This fuction returns a Vector of Variables used in the input and 
  ** output of this capability.
  ** @return the Vector of Variables used in the input and 
  **   output of this capability
  */
  protected Vector getVars() {
    Vector res;
    res = (superAct != null) ? superAct.getVars() : new Vector();
    if (input != null)
      for (Enumeration ipse=input.elements(); ipse.hasMoreElements(); )
	((ParamSpec)ipse.nextElement()).theFiller.getVars(res);
    if (output != null)
      for (Enumeration opse=output.elements(); opse.hasMoreElements(); )
	((ParamSpec)opse.nextElement()).theFiller.getVars(res);
    return res;
  }

  /** 
  ** This function converts this CDL capability description into 
  ** a printable (and parsable) String.
  ** @return the String representing this capability description
  */
  public String toString() {
    String res = "(";
    res += (isCapability) ? "capability" : "task";
    if (actionId != null) 
      res += " :action " + actionId;
    if (superAct != null) 
      res += " :isa " + superAct.actionId;
    if ((properties != null) && (!properties.isEmpty()))
      res += " :properties " + toListString(properties);
    res += " :state-language " + stateLangId;
    if ((input != null) && (!input.isEmpty()))
      res += " :input " + toListString(input);
    if ((output != null) && (!output.isEmpty()))
      res += " :output " + toListString(output);
    if ((inputConstrs != null) && (!inputConstrs.isEmpty()))
      res += " :input-constraints " + toListString(inputConstrs);
    if ((outputConstrs != null) && (!outputConstrs.isEmpty()))
      res += " :output-constraints " + toListString(outputConstrs);
    if ((ioConstrs != null) && (!ioConstrs.isEmpty()))
      res += " :io-constraints " + toListString(ioConstrs);
    return res + ')';
  }

  /**
  ** This function takes a Vector and converts it to a String in round
  ** brackets. Elements are seperated by spaces.
  ** @return a Lisp-like String with the elements in this Vector
  */
  static private String toListString(Vector list) {
    Enumeration e = list.elements();
    String res = "(" + e.nextElement().toString();
    while (e.hasMoreElements())
      res += " " + e.nextElement().toString();
    return res + ')';
  }

  /**
  ** This function instantiates this capability description based on
  ** its super-action.
  ** @return the instantiated version of this capability
  */
  public CDLDescription instantiate() throws FormalismException {
    CDLInterpreter.traceln("Instantiating: " + this);
    if (superAct == null)
      return (CDLDescription)clone();

    //System.out.println("Instantiating: " + this);
    //System.out.println("From: " + superAct);
    CDLDescription res = superAct.instantiate();
    CDLInterpreter.traceln("Instantiated super-action: " + res);
    res.actionId = null;
    res.superAct = null;
    if (properties != null) {
      if (res.properties == null)
	res.properties = (Vector)properties.clone();
      else
	for (Enumeration pe=properties.elements(); pe.hasMoreElements(); )
	  res.properties.addElement(pe.nextElement());
    }
    Substitution s = new Substitution();
    res.input = amend(res.input, input, s);
    res.output = amend(res.output, output, s);
    tryLockVars(s, res.getVars());
    CDLInterpreter.traceln("Parameter-unifying substitution: " + s);
    res.instantiateParams(s);
    res.inputConstrs = mergeConstraints(res.inputConstrs, inputConstrs, s);
    res.outputConstrs = mergeConstraints(res.outputConstrs, outputConstrs, s);
    res.ioConstrs = mergeConstraints(res.ioConstrs, ioConstrs, s);
    //System.out.println("Result: " + res);
    CDLInterpreter.traceln("Instanciated CDL description: " + res);
    return res;
  }

  private Vector amend(Vector superParams, Vector newParams, Substitution s) {
    CDLInterpreter.traceln("Amending " + superParams + " with " + newParams 
        + " under " + s);
    if ((superParams == null) && (newParams == null))
      return null;
    if (superParams == null)
      return (Vector)newParams.clone();
    if (newParams == null)
      return (Vector)superParams.clone();

    Vector res = (Vector)superParams.clone();
    for (Enumeration pe=newParams.elements(); pe.hasMoreElements(); ) {
      ParamSpec newP = (ParamSpec)pe.nextElement();
      ParamSpec rps = findParamSpec(newP.theRole, res);
      if (rps == null) {
	res.addElement(newP);
	CDLInterpreter.traceln("Additional parameter: " + newP);
      }
      else {
	try {
	  s.unify(((VarTerm)rps.theFiller).getVar(), newP.theFiller);
	  CDLInterpreter.traceln("Instantiating: " + rps);
	} catch (UnificationException ue) {
	  throw new UnknownError(
	      "Unknown capability instantiation problem.");
	}
      }
    }
    return res;
  }

  static private ParamSpec findParamSpec(Symbol role, Vector params) {
    for (Enumeration pse=params.elements(); pse.hasMoreElements(); ) {
      ParamSpec ps = (ParamSpec)pse.nextElement();
      if (role.equals(ps.theRole))
	return ps;
    }
    return null;
  }

  static private void tryLockVars(Substitution s, Vector v) {
    for (Enumeration ve=v.elements(); ve.hasMoreElements(); ) {
      try {
	s.lockVariable((Variable)ve.nextElement());
      } catch (UnificationException ue) {
      }
    }
  }

  private void instantiateParams(Substitution s) {
    if (input != null) {
      Vector newI = new Vector();
      for (Enumeration ie=input.elements(); ie.hasMoreElements(); )
	newI.addElement(((ParamSpec)ie.nextElement()).instantiate(s));
      input = newI;
    }
    if (output != null) {
      Vector newO = new Vector();
      for (Enumeration oe=output.elements(); oe.hasMoreElements(); )
	newO.addElement(((ParamSpec)oe.nextElement()).instantiate(s));
      output = newO;
    }
  }

  static private Vector mergeConstraints(Vector c1, Vector c2, 
      Substitution s) throws FormalismException {
    if ((c1 == null) && (c2 == null))
      return null;
    Vector res = new Vector();
    if (c1 != null)
      addConstraints(res, c1, s);
    if (c2 != null)
      addConstraints(res, c2, s);
    return res;
  }

  static private void addConstraints(Vector res, Vector c1, Substitution s)
      throws FormalismException {
    for (Enumeration ce=c1.elements(); ce.hasMoreElements(); ) {
      KRSentence krs = (KRSentence)ce.nextElement();
      try {
	res.addElement(krs.getClass().getMethod("clone", new Class[]
            {Class.forName("JavaAgent.resource.fopl.Substitution")}).
	    invoke(krs, new Object[]{s}));
      } catch (Exception e) {
	throw new FormalismException(
            "Cloning with Substitution not supported by " + krs.getClass());
      }
    }
  }

  public Vector getInputVars() throws Exception {
    Vector res = new Vector();
    for (Enumeration ipe=input.elements(); ipe.hasMoreElements(); )
      ((ParamSpec)ipe.nextElement()).theFiller.getVars(res);
    return res;
  }
    
  public Vector getOutputVars() throws Exception {
    Vector res = new Vector();
    for (Enumeration ipe=output.elements(); ipe.hasMoreElements(); )
      ((ParamSpec)ipe.nextElement()).theFiller.getVars(res);
    return res;
  }
    
  public Vector getInputConstrs() {
    return inputConstrs;
  }

  public Vector getOutputConstrs() {
    return outputConstrs;
  }

  public Vector getIOConstrs() {
    return ioConstrs;
  }

  /**
  ** the name of the agent that advertised the capability
  */
  protected String cAgent;

  /**
  ** indicates whether this is a capability or a task description
  */
  protected boolean isCapability;

  /**
  ** the resource identifier for the state language
  */
  protected String stateLangId;

  /**
  ** the class that represents the language for states
  */
  protected Class stateLangClass;

  /**
  ** the action identifier for this capability
  */
  protected String actionId;

  /**
  ** the action from which this one inherits (or null)
  */
  protected CDLDescription superAct;

  /**
  ** These Vectors contain the description of the input and output (elements
  ** should be of type ParamSpec below) and the constraints on input and 
  ** output states (elements should be members of the Class in the
  ** variable stateLangClass).
  */
  private Vector properties, input, output, inputConstrs, outputConstrs, 
      ioConstrs;
}


class ParamSpec {

  ParamSpec(Symbol role, Term filler) {
    theRole = role;
    theFiller = filler;
  }

  ParamSpec instantiate(Substitution s) {
    return new ParamSpec(theRole, theFiller.clone(s));
  }

  public String toString() {
    return "(" + theRole + ' ' + theFiller + ')';
  }

  Symbol theRole;
  Term theFiller;
}
