// file: $isip/class/dstr/BiGraph/BiGraph.h
// version: $Id: BiGraph.h 10636 2007-01-26 22:18:09Z tm334 $
//

// make sure definitions are only made once
//
#ifndef ISIP_BI_GRAPH
#define ISIP_BI_GRAPH

// isip include files
//
#ifndef ISIP_DOUBLE_LINKED_LIST
#include <DoubleLinkedList.h>
#endif

#ifndef ISIP_BI_COLOR_HASH
#include <BiColorHash.h>
#endif

#ifndef ISIP_BOOLEAN
#include <Boolean.h>
#endif

#ifndef ISIP_TRIPLE
#include <Triple.h>
#endif

#ifndef ISIP_PAIR
#include <Pair.h>
#endif

#ifndef ISIP_LONG
#include <Long.h>
#endif

#ifndef ISIP_BIGRAPH_VERTEX
#include <BiGraphVertex.h>
#endif

// forward class definitions:
//  we must define the BiGraphVertex class here first because the header files
//  might be short-circuited by the ifndef.
//
template<class TObject> class DiGraph;
template<class TObject> class GraphVertex;
template<class TObject> class GraphArc;
template<class TObject> class BiColorHash;
template<class TObject> class BiGraphVertex;

// BiGraph: a generic directed-graph template class. it is implemented
//  with something close to an adjacency list, with data held on the
//  vertices. each vertex holds both a TObject* and a list of all
//  emanating arcs. a graph can be in either USER or SYSTEM allocated
//  mode, for a USER-allocated list the TObject data is never copied.
//
template<class TObject>
class BiGraph : public DoubleLinkedList< BiGraphVertex<TObject> > {

  //---------------------------------------------------------------------------
  //
  // public constants
  //
  //---------------------------------------------------------------------------
public:
  
  // define the class name
  //
  static const String CLASS_NAME;
  
  //----------------------------------------
  //
  // i/o related constants
  //
  //----------------------------------------  
  
  static const String DEF_PARAM;
  static const String PARAM_VERTICES;
  static const String PARAM_ARCS;
  static const String PARAM_WEIGHTED;
  static const String START_NAME;
  static const String TERM_NAME;
  
  //----------------------------------------
  //
  // dummy objects for start and end vertices
  //
  //----------------------------------------  

  static const TObject START_OBJ;
  static const TObject TERM_OBJ;

  //----------------------------------------
  //
  // indeces for start and end vertices
  //
  //----------------------------------------

  static const long START_INDEX = -1;
  static const long TERM_INDEX = -2;  
  
  //----------------------------------------
  //
  // default values and arguments
  //
  //----------------------------------------

  // default values
  //
  static const boolean DEF_IS_WEIGHTED = true;

  // default arguments to methods
  //
  
  //----------------------------------------
  //
  // error codes
  //
  //----------------------------------------  
  
  static const long ERR = 41200;
  static const long ERR_EPSCYC = 41201;
  static const long ERR_MULPAR = 41202;
  
  //---------------------------------------------------------------------------
  //
  // protected data
  //
  //---------------------------------------------------------------------------
protected:

  // define the object used to hold graph topology in read and write
  //
  typedef Triple< Pair<Long, Long>, Float, Boolean> TopoTriple;
  typedef HashTable< String, BiGraphVertex<TObject> > ReadHash;
  
  // each graph has a dummy start vertex
  //
  BiGraphVertex<TObject>* start_vertex_d;

  // each graph has a dummy terminal vertex
  //
  BiGraphVertex<TObject>* term_vertex_d;

  // each graph has a hash table that associates a color with the
  // graph vertices that tell us if the vertex has been
  // visited. unless explicitely allocated via the makeBiColorHash
  // method this will be a null pointer and all color methods will
  // fail.
  //
  BiColorHash<TObject>* chash_d;
  
  // is the graph weighted?
  //
  Boolean is_weighted_d;

  // the allocation mode
  //
  DstrBase::ALLOCATION alloc_d;  
  
  // debugging parameters
  //
  static Integral::DEBUG debug_level_d;
  
  // define the memory manager
  //
  static MemoryManager mgr_d;
  
  //---------------------------------------------------------------------------
  //
  // required public methods
  //
  //---------------------------------------------------------------------------
public:

  // static methods
  //  diagnose method is moved outside the class header file and
  //  defined in the BiGraphDiagnose.h in order to avoid issues
  //  related to preprocessing of the diagnose code.
  //
  static const String& name();

  // method: setDebug
  //
  static boolean setDebug(Integral::DEBUG debug_level) {
    debug_level_d = debug_level;
    return true;
  }

  // other debug methods
  //
  boolean debug(const unichar* message) const;

  // destructor
  //
  ~BiGraph();

  // default constructor
  //
  BiGraph(DstrBase::ALLOCATION alloc = DstrBase::DEF_ALLOCATION);

  // copy constructor
  //
  BiGraph(const BiGraph<TObject>& copy_graph);
  
  // assign methods:
  //  having the same vertex appear in two different graphs is not allowed.
  //  thus, memory is created for copied vertices even if the graph is in
  //  user-allocating mode!
  //
  boolean assign(const BiGraph<TObject>& copy_graph);
  boolean assign(const DiGraph<TObject>& copy_graph);  
  boolean assign(DoubleLinkedList<TObject>& data,
		 DoubleLinkedList<TopoTriple>& arcs);
  
  // method: operator=
  //
  BiGraph<TObject>& operator=(const BiGraph<TObject>& arg) {
    assign(arg);
    return *this;
  }

  // equality method
  //
  boolean eq(const BiGraph<TObject>& compare_graph) const;
  
  // i/o methods
  //
  long sofSize() const;
  
  // method: read
  //
  boolean read(Sof& sof, long tag) {
    return read(sof, tag, name());
  }
  boolean read(Sof& sof, long tag, const String& name);

  // method: write
  //
  boolean write(Sof& sof, long tag) const {
    return write(sof, tag, name());
  }
  boolean write(Sof& sof, long tag, const String& name) const;

  boolean readData(Sof& sof, const String& pname = DEF_PARAM,
		   long size = SofParser::FULL_OBJECT, boolean param = true,
                   boolean nested = false);
  boolean writeData(Sof& sof, const String& pname = DEF_PARAM) const;

  boolean readDataText(Sof& sof, const String& pname = DEF_PARAM,
		       long size = SofParser::FULL_OBJECT,
		       boolean param = true,
		       boolean nested = false);
  boolean readDataBinary(Sof& sof);
  
  boolean writeDataText(Sof& sof, const String& pname,
			DoubleLinkedList<TObject>& dlist,
			DoubleLinkedList<TopoTriple>& alist) const;
  boolean writeDataBinary(Sof& sof, const String& pname,
			  DoubleLinkedList<TObject>& dlist,
			  DoubleLinkedList<TopoTriple>& alist) const;    
   
  // method: new
  //
  void* operator new(size_t size) {
    return mgr_d.get();
  }

  // method: new[]
  // 
  void* operator new[](size_t size) {
    return mgr_d.getBlock(size);
  }

  // method: delete
  //  
  void operator delete(void* ptr) {
    mgr_d.release(ptr);
  }

  // method: delete[]
  //  
  void operator delete[](void* ptr) {
    mgr_d.releaseBlock(ptr);
  }

  // method: setGrowSize
  //  
  static boolean setGrowSize(long grow_size) {
    return mgr_d.setGrow(grow_size);
  }

  // other memory management methods
  //  
  boolean clear(Integral::CMODE cmode = Integral::DEF_CMODE);

  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  graph manipulation methods
  //
  //---------------------------------------------------------------------------

  // method: insertArc
  //
  boolean insertArc(long start_index, long end_index,
		    boolean is_epsilon = BiGraphArc<TObject>::DEF_EPSILON,
		    float weight = BiGraphArc<TObject>::DEF_WEIGHT) {
    return insertArc(getPosition(start_index), getPosition(this->end_vertex),
		     is_epsilon, weight);
  }

  // other arc insertion methods
  //
  boolean insertArc(BiGraphVertex<TObject>* start_vertex,
		    BiGraphVertex<TObject>* end_vertex,
		    boolean is_epsilon = BiGraphArc<TObject>::DEF_EPSILON,
		    float weight = BiGraphArc<TObject>::DEF_WEIGHT);
  
  // method: removeArc
  //
  boolean removeArc(long start_index, long end_index) {
    return removeArc(getPosition(start_index), getPosition(this->end_vertex));
  }

  // other arc removal methods
  //  
  boolean removeArc(BiGraphVertex<TObject>* start_vertex,
		    BiGraphVertex<TObject>* end_vertex);

  // insert vertex methods
  //
  BiGraphVertex<TObject>* insertVertex(TObject* arg);

  // remove vertex methods
  //
  boolean removeVertex();  
  boolean removeVertex(TObject*& arg);
  
  // item containment methods:
  //
  boolean find(const TObject* obj);
  boolean find(const BiGraphVertex<TObject>* vertex);
  boolean contains(const TObject* obj) const;
  boolean contains(const BiGraphVertex<TObject>* vertex) const;  
  
  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  graph property methods
  //
  //---------------------------------------------------------------------------
    
  // method: getStart
  //
  BiGraphVertex<TObject>* getStart() const {
    return start_vertex_d;
  }

  // method: getTerm
  //
  BiGraphVertex<TObject>* getTerm() const {
    return term_vertex_d;
  }

  // this method extracts the structure of the graph and places them
  // in two lists. the first list contains the vertices and the second
  // list contains the arcs between the vertices.
  //
  boolean get(DoubleLinkedList<TObject>& data,
	      DoubleLinkedList<TopoTriple>& arcs) const;
  
  // method: isWeighted
  //
  boolean isWeighted() const {
    return is_weighted_d;
  }

  // method: setWeighted
  //
  boolean setWeighted(boolean is_weighted = true) {
    is_weighted_d = is_weighted;
    return true;
  }
  
  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  graph ordering methods
  //
  //---------------------------------------------------------------------------
  
  // topological sort methods
  //
  boolean topologicalSort(DoubleLinkedList<TObject>& sorted_data,
			  boolean partial = false);

  // method: getColor
  //
  Integral::COLOR getColor(const BiGraphVertex<TObject>* v) const {
    return chash_d->get(v);
  }

  // method: setColor
  //
  boolean setColor(const BiGraphVertex<TObject>* v, Integral::COLOR col) {
    return chash_d->set(v, col);
  }
  
  // method: releaseColorHash
  //
  boolean releaseColorHash() {
    chash_d->clear(Integral::RESET);    
    if (chash_d != (BiColorHash<TObject>*)NULL) {
      delete chash_d;
      chash_d = (BiColorHash<TObject>*)NULL;
    }
    return true;
  }
  
  // other color methods
  //
  boolean invertColor();
  boolean makeColorHash(Integral::COLOR col = Integral::DEF_COLOR);
  boolean setColor(Integral::COLOR col);
  boolean isColor(Integral::COLOR col);
  boolean replaceColor(Integral::COLOR old_col,
		       Integral::COLOR new_col);

  // methods to do simple depth first search
  //
  boolean dfsVisit(BiGraphVertex<TObject>& vertex);

  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  graph allocation mode methods
  //
  //---------------------------------------------------------------------------
  
  // method: getAllocationMode
  //
  DstrBase::ALLOCATION getAllocationMode() const {
    return alloc_d;
  }

  // method: setAllocationMode
  //
  boolean setAllocationMode(DstrBase::ALLOCATION alloc) {
    alloc_d = alloc;
    return true;
  }  

  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  double linked list methods
  //
  //---------------------------------------------------------------------------
  // the BiGraph class derives the DoubleLinkedList bases class which
  // is used as the primary underlying container class. to prevent the
  // user from circumventing the BiGraph's interface and interacting
  // directly with the DoubleLinkedList we use private inheritance.
  // we define here the methods of the DoubleLinkedList interface that
  // the user is permitted access to.
  //
  using DoubleLinkedList< BiGraphVertex<TObject> >::length;
  using DoubleLinkedList< BiGraphVertex<TObject> >::gotoFirst;
  using DoubleLinkedList< BiGraphVertex<TObject> >::gotoNext;
  using DoubleLinkedList< BiGraphVertex<TObject> >::gotoPrev;
  using DoubleLinkedList< BiGraphVertex<TObject> >::getCurr;
  using DoubleLinkedList< BiGraphVertex<TObject> >::getFirst;
  using DoubleLinkedList< BiGraphVertex<TObject> >::getLast;
  using DoubleLinkedList< BiGraphVertex<TObject> >::gotoMark;
  using DoubleLinkedList< BiGraphVertex<TObject> >::setMark;
  using DoubleLinkedList< BiGraphVertex<TObject> >::gotoPosition;
  using DoubleLinkedList< BiGraphVertex<TObject> >::getPosition;
  
  //---------------------------------------------------------------------------
  //
  // private methods
  //
  //---------------------------------------------------------------------------
private:

  // methods to split writeData:
  //  the readData needed to be broken up
  //
  boolean readVertexDataText(Sof& sof_a, SofParser& parser,
			     ReadHash& hash) const;
  boolean readArcDataText(Sof& sof_a, SofParser& parser,
			  ReadHash& hash) const;
  
  // methods to split writeData:
  //  the writeData needed to be broken up
  //
  boolean writeVertexDataText(Sof& sof,
			      DoubleLinkedList<TObject>& dlist) const;
  boolean writeArcDataText(Sof& sof, 
			   DoubleLinkedList<TopoTriple>& alist) const;

  // methods for topological sort:
  //  topological sort needs some customized DFS routines
  //
  boolean dfsVisitTS(DoubleLinkedList<TObject>& output,
		     BiGraphVertex<TObject>& vertex);
};

//-----------------------------------------------------------------------------
//
// we define non-integral constants at the end of class definition for
// templates (for non-templates these are defined in the default constructor)
//      
//-----------------------------------------------------------------------------

// constants: required constants such as the class name
//
template <class TObject>
const String BiGraph<TObject>::CLASS_NAME(L"BiGraph");

// constants: i/o related constants
//
template <class TObject>
const String BiGraph<TObject>::DEF_PARAM(L"");

template <class TObject>
const String BiGraph<TObject>::PARAM_VERTICES(L"vertices");

template <class TObject>
const String BiGraph<TObject>::PARAM_ARCS(L"arcs");

template <class TObject>
const String BiGraph<TObject>::PARAM_WEIGHTED(L"weighted");

// constants: define non-integral constants in the default constructor
//
template <class TObject>
const String BiGraph<TObject>::START_NAME(L"S");

template <class TObject>
const String BiGraph<TObject>::TERM_NAME(L"T");

template <class TObject>
const TObject BiGraph<TObject>::START_OBJ;

template <class TObject>
const TObject BiGraph<TObject>::TERM_OBJ;

// static instantiations: debug level and memory manager
//
template <class TObject>
Integral::DEBUG BiGraph<TObject>::debug_level_d = Integral::NONE;

template <class TObject>
MemoryManager BiGraph<TObject>::mgr_d(sizeof(BiGraph<TObject>),
				    CLASS_NAME);

// below are all the methods for the BiGraph template class
//      

// ---------------------------------------------------------------------
//
// required static methods
//
//----------------------------------------------------------------------

// method: name
//
// arguments: none
//
// return: a static String& containing the class name
//
// this method returns the class name
//
template<class TObject>
const String& BiGraph<TObject>::name() {

  // create the static name string for this class and return it
  //
  static String cname(CLASS_NAME);
  cname.clear(Integral::RESET);
  cname.concat(CLASS_NAME);
  cname.concat(L"<");
  cname.concat(TObject::name());
  cname.concat(L">");

  // return the name
  //
  return cname;
}

// ---------------------------------------------------------------------
//
// required debug methods
//
//----------------------------------------------------------------------

// method: debug
//
// arguments:
//  const unichar* message: (input) information message
//
// return: a boolean value indicating status
//
// this method dumps the contents of an object to the console
// 
template<class TObject>
boolean BiGraph<TObject>::debug(const unichar* message_a) const {

  // build a debug string
  //
  String val;
  String output;
  output.debugStr(name(), message_a, L"");
  Console::put(output);
  
  Console::increaseIndention();

  // dump the this pointer
  //
  val.assign(this);
  output.debugStr(name(), message_a, L"this", val);
  Console::put(output);
  
  // dump allocation mode
  //
  output.debugStr(name(), message_a, L"alloc_d",
		  NameMap::ALLOCATION_MAP((long)alloc_d));
  Console::put(output);
  
  // dump is_weighted
  //
  val.assign(is_weighted_d);
  output.debugStr(name(), message_a, L"is_weighted_d", val);
  Console::put(output);
  
  // dump a pointer to the start vertex
  //
  val.assign(start_vertex_d);
  output.debugStr(name(), message_a, L"start_vertex_d", val);
  Console::put(output);

  // print the start vertex information
  //
  start_vertex_d->debug(L"start_vertex_d");  

  // call the parent debug method to debug the list of vertices itself
  //
  DoubleLinkedList< BiGraphVertex<TObject> >::debug(L"vertices");

  // dump a pointer to the terminal vertex
  //
  val.assign(term_vertex_d);
  output.debugStr(name(), message_a, L"term_vertex_d", val);
  Console::put(output);
  
  // print the terminal vertex information
  //
  term_vertex_d->debug(L"term_vertex_d");
  
  // that's it for sub-items, decrease indentation
  //
  Console::decreaseIndention();

  // exit gracefully
  // 
  return true;
}

//------------------------------------------------------------------------
//
// required destructor/constructor(s)
//
//-----------------------------------------------------------------------

// method: destructor
//
// arguments: none
//
// return: none
//
// this is the default destructor for the BiGraph class
//
template<class TObject>
BiGraph<TObject>::~BiGraph() {

  // clear the BiGraph
  //
  BiGraph<TObject>::clear(Integral::RESET);

  // delete the start and terminal vertices
  //
  delete start_vertex_d;
  start_vertex_d = (BiGraphVertex<TObject>*)NULL;
  delete term_vertex_d;
  term_vertex_d = (BiGraphVertex<TObject>*)NULL;

  // delete the color hash table
  //
  if (chash_d != (BiColorHash<TObject>*)NULL) {
    delete chash_d;
    chash_d = (BiColorHash<TObject>*)NULL;
  }
}

// method: default constructor
//
// arguments:
//  ALLOCATION alloc: (input) allocation mode for nodes
//
// return: none
//
// this is the default constructor for the BiGraph class
//
template<class TObject>
BiGraph<TObject>::BiGraph(DstrBase::ALLOCATION alloc_a) {

  // initialize internal data
  //
  is_weighted_d = DEF_IS_WEIGHTED;

  // set the allocation flag
  //
  alloc_d = alloc_a;
  
  // create and initialize the start and terminal vertices
  //
  start_vertex_d = new BiGraphVertex<TObject>();
  term_vertex_d = new BiGraphVertex<TObject>();

  start_vertex_d->setItem((TObject*)&START_OBJ);
  term_vertex_d->setItem((TObject*)&TERM_OBJ);

  start_vertex_d->setParentGraph(this);
  term_vertex_d->setParentGraph(this);

  // initialize the color hash table
  //
  chash_d = (BiColorHash<TObject>*)NULL;
  
  // set the allocation mode of the parent class
  //
  DoubleLinkedList< BiGraphVertex<TObject> >::setAllocationMode(DstrBase::USER);
}

// method: copy constructor
//
// arguments:
//  BiGraph<TObject>& arg: (input) the graph to copy
//
// return: none
//
// this is the copy constructor for the BiGraph class. note that having the
// same vertex appear in two different graphs is not allowed. thus, memory is
// created for copied vertices even if the graph is in user-allocating
// mode!
//
template<class TObject>
BiGraph<TObject>::BiGraph(const BiGraph<TObject>& copy_graph_a) {

  // create and initialize the start and terminal vertices
  //
  start_vertex_d = new BiGraphVertex<TObject>();
  term_vertex_d = new BiGraphVertex<TObject>();

  start_vertex_d->setItem((TObject*)&START_OBJ);
  term_vertex_d->setItem((TObject*)&TERM_OBJ);

  start_vertex_d->setParentGraph(this);
  term_vertex_d->setParentGraph(this);

  // initialize the color hash table
  //
  chash_d = (BiColorHash<TObject>*)NULL;
  
  // set the allocation mode of the parent class
  //
  DoubleLinkedList< BiGraphVertex<TObject> >::setAllocationMode(DstrBase::USER);

  // intialize the allocation flag to default
  //
  alloc_d = DstrBase::DEF_ALLOCATION;
  
  // call the copy assign method
  //
  assign(copy_graph_a);
}

//-------------------------------------------------------------------------
//
//  required assign methods
//
//-------------------------------------------------------------------------

// method: assign
//
// arguments:
//  BiGraph<TObject>& copy_graph: (input) the graph to copy
//
// return: a boolean value indicating status
//
// this is the assign method for the BiGraph class. we can't just copy
// the data over since the arcs make connections by pointer (and we
// can't share vertices across two graphs), hence we must manually
// insert every vertex and every arc. new vertices are created, but if
// the graph is in USER mode the tobject is not copied.
//
template<class TObject>
boolean BiGraph<TObject>::assign(const BiGraph<TObject>& arg_a) {

  // first cleanup the list
  //
  if (!clear(Integral::RESET)) {
    return Error::handle(name(), L"assign", Error::ARG, __FILE__, __LINE__);
  }
  
  // copy the weighting flag.
  //
  is_weighted_d = arg_a.is_weighted_d;

  // copy the allocation flag
  //
  alloc_d = arg_a.alloc_d;
  
  // save the state of the input graph
  //
  const_cast<BiGraph<TObject>& >(arg_a).setMark();
  
  // we need to build a cross-referencing scheme to make sure we cover
  // all arcs and all nodes. the (+2) is for the start and terminal
  // vertices since they are NOT included in the list
  //
  long num_vertices = arg_a.length() + 2;
  
  BiGraphVertex<TObject>* copy_sym_ptrs[num_vertices];
  BiGraphVertex<TObject>* this_sym_ptrs[num_vertices];

  // set the start and terminal vertices
  //
  copy_sym_ptrs[0] = arg_a.getStart();
  copy_sym_ptrs[num_vertices - 1] = arg_a.getTerm();
  
  // loop through the list of vertices, and number them
  //
  const_cast<BiGraph<TObject>& >(arg_a).gotoFirst();

  for (long i = 1; i < num_vertices - 1; i++) {

    // get the current node in the graph and assign it to the array
    //
    copy_sym_ptrs[i] = const_cast<BiGraphVertex<TObject>* >
      (arg_a.getCurr());

    // move to the next node in the graph
    //
    const_cast<BiGraph<TObject>& >(arg_a).gotoNext();
  }

  // before we assign the input graph we need to clear the current graph
  //
  this->clear(Integral::RESET);
  
  // set the start and terminal vertices
  //
  this_sym_ptrs[0] = getStart();
  this_sym_ptrs[num_vertices - 1] = getTerm();  

  for (long i = 1; i < num_vertices - 1; i++) {

    // create a new graph vertex
    //
    this_sym_ptrs[i] = this->insertVertex(copy_sym_ptrs[i]->getItem());
  }

  // now build the arclists for each node
  //
  for (long i = 0; i < num_vertices; i++) {

    // get the vertex in question
    //
    BiGraphVertex<TObject>* tmp_copy_vert = copy_sym_ptrs[i];

    // loop over the copy graph's arclist
    //
    boolean arcs_remain = tmp_copy_vert->gotoFirstChild();

    while (arcs_remain) {

      // setup temporary variables
      //
      long dest_index = -1;
      BiGraphArc<TObject>* tmp_arc = tmp_copy_vert->getCurrChild();
      BiGraphVertex<TObject>* dest_vertex = tmp_arc->getVertex();

      // find the destination vertex index
      //
      for (long j = 0; j < num_vertices; j++) {
	if (dest_vertex == copy_sym_ptrs[j]) {
	  dest_index = j;
	  break;
	}
      }

      if (dest_index == -1) {
	return Error::handle(name(), L"assign", Error::ARG,
			     __FILE__, __LINE__);
      }

      // link the vertices in the new graph
      //
      insertArc(this_sym_ptrs[i], this_sym_ptrs[dest_index],
		tmp_arc->getEpsilon(), tmp_arc->getWeight());

      // goto the next arc
      //
      arcs_remain = tmp_copy_vert->gotoNextChild();
    }
  }

  // put the copy graph back in its original state
  //
  const_cast<BiGraph<TObject>& >(arg_a).gotoMark();

  // exit gracefully
  //
  return true;
}

// method: assign
//
// arguments:
//  DiGraph<TObject>& copy_graph: (input) the graph to copy
//
// return: a boolean value indicating status
//
// this is the assign method for the BiGraph class. we can't just copy
// the data over since the arcs make connections by pointer (and we
// can't share vertices across two graphs), hence we must manually
// insert every vertex and every arc. new vertices are created, but if
// the graph is in USER mode the tobject is not copied.
//
template<class TObject>
boolean BiGraph<TObject>::assign(const DiGraph<TObject>& arg_a) {

  // first cleanup the list
  //
  if (!clear(Integral::RESET)) {
    return Error::handle(name(), L"assign", Error::ARG, __FILE__, __LINE__);
  }
  
  // copy the weighting flag.
  //
  is_weighted_d = arg_a.isWeighted();

  // copy the allocation flag
  //
  alloc_d = arg_a.getAllocationMode();
  
  // save the state of the input graph
  //
  const_cast<DiGraph<TObject>& >(arg_a).setMark();
  
  // we need to build a cross-referencing scheme to make sure we cover
  // all arcs and all nodes. the (+2) is for the start and terminal
  // vertices since they are NOT included in the list
  //
  long num_vertices = arg_a.length() + 2;
  
  GraphVertex<TObject>* copy_sym_ptrs[num_vertices];
  BiGraphVertex<TObject>* this_sym_ptrs[num_vertices];

  // set the start and terminal vertices
  //
  copy_sym_ptrs[0] = arg_a.getStart();
  copy_sym_ptrs[num_vertices - 1] = arg_a.getTerm();
  
  // loop through the list of vertices, and number them
  //
  const_cast<DiGraph<TObject>& >(arg_a).gotoFirst();

  for (long i = 1; i < num_vertices - 1; i++) {

    // get the current node in the graph and assign it to the array
    //
    copy_sym_ptrs[i] = const_cast<GraphVertex<TObject>* >
      (arg_a.getCurr());

    // move to the next node in the graph
    //
    const_cast<DiGraph<TObject>& >(arg_a).gotoNext();
  }

  // before we assign the input graph we need to clear the current graph
  //
  this->clear(Integral::RESET);
  
  // set the start and terminal vertices
  //
  this_sym_ptrs[0] = getStart();
  this_sym_ptrs[num_vertices - 1] = getTerm();  

  for (long i = 1; i < num_vertices - 1; i++) {

    // create a new graph vertex
    //
    this_sym_ptrs[i] = this->insertVertex(copy_sym_ptrs[i]->getItem());
  }

  // now build the arclists for each node
  //
  for (long i = 0; i < num_vertices; i++) {

    // get the vertex in question
    //
    GraphVertex<TObject>* tmp_copy_vert = copy_sym_ptrs[i];

    // loop over the copy graph's arclist
    //
    boolean arcs_remain = tmp_copy_vert->gotoFirst();

    while (arcs_remain) {

      // setup temporary variables
      //
      long dest_index = -1;
      GraphArc<TObject>* tmp_arc = tmp_copy_vert->getCurr();
      GraphVertex<TObject>* dest_vertex = tmp_arc->getVertex();

      // find the destination vertex index
      //
      for (long j = 0; j < num_vertices; j++) {
	if (dest_vertex == copy_sym_ptrs[j]) {
	  dest_index = j;
	  break;
	}
      }

      if (dest_index == -1) {
	return Error::handle(name(), L"assign", Error::ARG,
			     __FILE__, __LINE__);
      }

      // link the vertices in the new graph
      //
      insertArc(this_sym_ptrs[i], this_sym_ptrs[dest_index],
		tmp_arc->getEpsilon(), tmp_arc->getWeight());

      // goto the next arc
      //
      arcs_remain = tmp_copy_vert->gotoNext();
    }
  }

  // put the copy graph back in its original state
  //
  const_cast<DiGraph<TObject>& >(arg_a).gotoMark();

  // exit gracefully
  //
  return true;
}

// method: assign
//
// arguments:
//  DoubleLinkedList<TObject>& data: (input) list of vertex data elements
//  DoubleLinkedList<TopoTriple> arcs: (input) list of arcs between vertices
//
// return: a boolean value indicating status
//
// this is a quick assign method for the BiGraph class. this method takes
// a ordered list of data elements for the graph vertices and a list of
// of arcs that connects the vertices in the graphs.
//
template<class TObject>
boolean BiGraph<TObject>::assign(DoubleLinkedList<TObject>& data_a,
				 DoubleLinkedList<TopoTriple>& arcs_a) {

  // declare local variables
  //
  long src = 0;
  long dst = 0;        
  float weight = 0.0;
  boolean epsilon = false;
  
  // determine if we need to do any work
  //
  if (data_a.length() == 0) {
    return Error::handle(name(), L"assign", Error::ARG, __FILE__, __LINE__);
  }

  // before we assign we need to clear the current graph
  //
  this->clear(Integral::RESET);
  
  // create vertices for the input data
  //
  BiGraphVertex<TObject>* vertices[data_a.length()];
  for (int i = 0; i < data_a.length(); i++) {
    data_a.gotoPosition(i);
    vertices[i] = insertVertex(data_a.getCurr());
  }

  // loop over all elements in the arc list
  //
  for (boolean more = arcs_a.gotoFirst(); more; more = arcs_a.gotoNext()) {

    // define the source and destination objects
    //
    BiGraphVertex<TObject>* src_vertex;
    BiGraphVertex<TObject>* dst_vertex;
    
    // determine the parameters of the arc
    //
    weight = (float)arcs_a.getCurr()->second();
    epsilon = (boolean)arcs_a.getCurr()->third();
    src = (long)arcs_a.getCurr()->first().first();
    dst = (long)arcs_a.getCurr()->first().second();
    
    // check if the source is the start vertex
    //
    if (src == START_INDEX) {
      src_vertex = getStart();
    }

    // check if the source is the term vertex
    //
    else if (src == TERM_INDEX) {
      src_vertex = getTerm();
    }
    
    else {
      src_vertex = vertices[src];
    }
    
    // check if the destination is the term vertex
    //
    if (dst == TERM_INDEX) {
      dst_vertex = getTerm();
    }

    // check if the destination is the start vertex
    //
    else if (dst == START_INDEX) {
      dst_vertex = getStart();
    }    

    else {
      dst_vertex = vertices[dst];
    }

    // insert an arc from the source to the destination vertices
    //
    insertArc(src_vertex, dst_vertex, epsilon, weight);
  }

  // exit gracefully
  //
  return true;
}

//------------------------------------------------------------------------
//
// required i/o methods
//
//------------------------------------------------------------------------

// method: sofSize
//
// arguments: none
//
// return: size of object as written to disk via the i/o methods
//
// this method determines the size of the object on disk. it has to
// nearly go through as much trouble as writing the object to
// determine the exact binary size.
//
template<class TObject>
long BiGraph<TObject>::sofSize() const {

  // declare lists to read the graph structure
  //
  DoubleLinkedList<TObject> dlist(DstrBase::USER);
  DoubleLinkedList<TopoTriple> alist;

  // start with the length
  //
  long bytes = is_weighted_d.sofSize();

  // get the structure of the graph
  //
  this->get(dlist, alist);

  // add the lengths of the lists
  //
  bytes += dlist.sofSize();
  bytes += alist.sofSize();  
  
  // return the size
  //
  return bytes;
}

// method: read
//
// arguments:
//  Sof& sof: (input) sof file object
//  long tag: (input) sof object instance tag
//  const String& name: (input) sof object instance name
//
// return: a boolean value indicating status
//
// this method has the object read itself from an Sof file
//
template<class TObject>
boolean BiGraph<TObject>::read(Sof& sof_a, long tag_a, const String& name_a) {
  
  // get the instance of the object from the Sof file
  //
  if (!sof_a.find(name_a, tag_a)) {
    return false;
  }

  // read the actual data from the sof file
  //
  if (!readData(sof_a)) {
    return false;
  }

  // exit gracefully
  //
  return true;
}

// method: write
//
// arguments:
//  Sof& sof: (input) sof file object
//  long tag: (input) sof object instance tag
//  const String& name: (input) sof object instance name
//
// return: a boolean value indicating status
//
// this method has the object write itself to an Sof file
//
template<class TObject>
boolean BiGraph<TObject>::write(Sof& sof_a, long tag_a,
			      const String& name_a) const {
  
  // declare a temporary size variable
  //
  long obj_size = 0;

  // switch on ascii or binary mode
  //
  if (sof_a.isText()) {

    // set the size to be dynamic
    //
    obj_size = Sof::ANY_SIZE;
  }
  else {

    // the size of the binary data to write
    //
    obj_size = sofSize();
  }
  
  // write the object into the sof file's index
  //
  if (!sof_a.put(name_a, tag_a, obj_size)) {
    return false;
  }
  
  // exit gracefully
  //
  return writeData(sof_a);
}

// method: readDataText
//
// arguments:
//  Sof& sof: (input) sof file object
//  const String& pname: (input) parameter name
//  long size: (input) size of the object
//  boolean param: (input) is the parameter specified?
//  boolean nested: (input) is this nested?
//
// return: a boolean value indicating status
//
// this method has the object read itself from an Sof file. it assumes
// that the Sof file is already positioned correctly.
//
template<class TObject>
boolean BiGraph<TObject>::readDataText(Sof& sof_a, const String& pname_a,
				       long size_a, boolean param_a,
				       boolean nested_a) {

  // declare local variables
  //
  String index_str;

  // first cleanup the list
  //
  if (!clear(Integral::RESET)) {
    return Error::handle(name(), L"readDataText", Error::READ,
			 __FILE__, __LINE__, Error::WARNING);
  }

  // local variables
  //
  SofParser parser;
  parser.setDebug(debug_level_d);
  
  // are we nested?
  //
  if (nested_a) {
    parser.setNest();
  }

  // load the parse
  //
  if (!parser.load(sof_a, size_a)) {
    return Error::handle(name(), L"readDataText", Error::READ,
			 __FILE__, __LINE__, Error::WARNING);
  }

  // read the two flags
  //
  if (!is_weighted_d.readData(sof_a, PARAM_WEIGHTED,
			      parser.getEntry(sof_a, PARAM_WEIGHTED))) {
    return Error::handle(name(), L"readDataText", Error::READ,
			 __FILE__, __LINE__, Error::WARNING);
  }

  if (debug_level_d >= Integral::ALL) {
    is_weighted_d.debug(L"is this weighted?");
  }

  // create a hash table so that we can access the graph vertices
  // via their indeces i.e., their positions in the graph
  //
  ReadHash hash_table(DstrBase::USER);

  // read in the vertex data
  //
  if (!readVertexDataText(sof_a, parser, hash_table)) {
    return Error::handle(name(), L"readDataText", Error::ARG,
			 __FILE__, __LINE__);
  }
  
  // read in the arc data
  //
  if (!readArcDataText(sof_a, parser, hash_table)) {
    return Error::handle(name(), L"readDataText", Error::ARG,
			 __FILE__, __LINE__);
  }
	  
  // exit gracefully
  //
  return true;
}

// method: readData
//
// arguments:
//  Sof& sof: (input) sof file object
//  const String& pname: (input) parameter name
//  long size: (input) size of the object
//  boolean param: (input) is the parameter specified?
//  boolean nested: (input) is this nested?
//
// return: a boolean value indicating status
//
// this method has the object read itself from an Sof file. it assumes
// that the Sof file is already positioned correctly.
//
template<class TObject>
boolean BiGraph<TObject>::readData(Sof& sof_a, const String& pname_a,
				   long size_a, boolean param_a,
				   boolean nested_a) {

  // when we are reading text data
  //
  if (sof_a.isText()) {
    return readDataText(sof_a, pname_a, size_a, param_a, nested_a);
  }

  // when we are reading binary data
  //
  else {
    return readDataBinary(sof_a);
  }
}

// method: writeData
//
// arguments:
//  Sof& sof: (input) sof file object
//  const String& pname: (input) parameter name
//
// return: a boolean value indicating status
//
// this method writes the object to the Sof file. it assumes that the
// Sof file is already positioned correctly.
//
template<class TObject>
boolean BiGraph<TObject>::writeData(Sof& sof_a,
				    const String& pname_a) const {
  
  // declare local variables and structures to read the graph
  //
  boolean status = false;
  DoubleLinkedList<TObject> dlist(DstrBase::USER);
  DoubleLinkedList<TopoTriple> alist;
  
  // read the current structure of the graph
  //
  this->get(dlist, alist);
  
  // when we are writing text data
  //
  if (sof_a.isText()) {
    status = writeDataText(sof_a, pname_a, dlist, alist);
  }

  // when we are writing binary data
  //
  else {
    status = writeDataBinary(sof_a, pname_a, dlist, alist);
  }

  // exit gracefully
  //
  return status;
}

// method: readDataBinary
//
// arguments:
//  Sof& sof: (input) sof file object
//
// return: a boolean value indicating status
//
// this method has the object read itself from an Sof file. it assumes
// that the Sof file is already positioned correctly.
//
template<class TObject>
boolean BiGraph<TObject>::readDataBinary(Sof& sof_a) {
  
  // declare some lists to read the data from
  //
  DoubleLinkedList<TObject> dlist;
  DoubleLinkedList<TopoTriple> alist;

  // first cleanup the list
  //
  if (!clear(Integral::RESET)) {
    return Error::handle(name(), L"readDataBinary",
			 Error::ARG, __FILE__, __LINE__);
  }
  
  // read the list graph vertex elements
  //
  dlist.readData(sof_a);

  // read the list of graph arcs and weights
  //
  alist.readData(sof_a);  

  // create a graph using the list of vertex elements and arcs
  //
  this->assign(dlist, alist);
 
  // exit gracefully
  //
  return true;
}

// method: writeDataText
//
// arguments:
//  Sof& sof: (input) sof file object
//  const String& pname: (input) parameter name
//  DoubleLinkedList<TObject>& dlist: (input) graph vertex elements
//  DoubleLinkedList<TopoTriple>& alist: (input) graph topology
//
// return: a boolean value indicating status
//
// this method writes the object to the Sof file. it assumes that the
// Sof file is already positioned correctly.
//
template<class TObject>
boolean BiGraph<TObject>::writeDataText(Sof& sof_a,
			  const String& pname_a,
			  DoubleLinkedList<TObject>& data_a,
			  DoubleLinkedList<TopoTriple>& arcs_a) const {

  // write a start string if necessary
  //
  sof_a.writeLabelPrefix(pname_a);
  
  // first write the flags
  //
  is_weighted_d.writeData(sof_a, PARAM_WEIGHTED);
    
  // first write the vertices data (this includes subgraph indexing)
  //
  writeVertexDataText(sof_a, data_a);

  // now write the arcs
  //
  writeArcDataText(sof_a, arcs_a);

  // put an end string if necessary
  //
  sof_a.writeLabelSuffix(pname_a);

  // exit gracefully
  //
  return true;
}

// method: writeDataBinary
//
// arguments:
//  Sof& sof: (input) sof file object
//  const String& pname: (input) parameter name
//  DoubleLinkedList<TObject>& dlist: (input) graph vertex elements
//  DoubleLinkedList<TopoTriple>& alist: (input) graph topology
//
// return: a boolean value indicating status
//
// this method writes the object to the Sof file. it assumes that the
// Sof file is already positioned correctly.
//
template<class TObject>
boolean BiGraph<TObject>::writeDataBinary(Sof& sof_a,
			    const String& pname_a,
			    DoubleLinkedList<TObject>& data_a,
			    DoubleLinkedList<TopoTriple>& arcs_a) const {

  // write the graph vertex elements
  //
  data_a.writeData(sof_a, pname_a);
  
  // write the graph arcs and weights
  //
  arcs_a.writeData(sof_a, pname_a);
  
  // exit gracefully
  //
  return true;
}

//-------------------------------------------------------------------------
//
//  required equality method
//
//-------------------------------------------------------------------------

// method: eq
//
// arguments:
//  const BiGraph<TObject>& graph: (input) the graph to compare
//
// return: boolean value indicating test of equivalence
//
// this method compares two graph to see if they are equal. it can't
// just call list-equality since all vertex pointers will be
// different.
//
template<class TObject>
boolean BiGraph<TObject>::eq(const BiGraph<TObject>& graph_a) const {

  // create lists to extract the graph structures
  //
  DoubleLinkedList<TObject> this_dlist;
  DoubleLinkedList<TopoTriple> this_alist;
  DoubleLinkedList<TObject> input_dlist;
  DoubleLinkedList<TopoTriple> input_alist;  

  // extract the structure of the current list
  //
  this->get(this_dlist, this_alist);
  
  // extract the structure of the input list
  //  
  graph_a.get(input_dlist, input_alist);

  // compare the lists for quality
  //
  if (!this_dlist.eq(input_dlist)) {
    return false;
  }
  if (!this_alist.eq(input_alist)) {
    return false;
  }  
  
  // we have reached this far so the graphs have to be equal
  //
  return true;
}

//-------------------------------------------------------------------------
//
//  required clear methods
//
//-------------------------------------------------------------------------

// method: clear
//
// arguments:
//  Integral::CMODE cmode_a: (input) clear mode
//
// return: a boolean value indicating status
//
// this method clears the reference to the internal data.
// 
template<class TObject>
boolean BiGraph<TObject>::clear(Integral::CMODE cmode_a) {

  // for RETAIN mode, call clear on every object but do not change
  // topology. for FREE mode we also need to call clear(FREE) on all
  // TObjects, but we will also delete the topology below.
  //
  if ((cmode_a == Integral::RETAIN) || (cmode_a == Integral::FREE)) {
    for (boolean more = gotoFirst(); more;
	 more = gotoNext()) {
      ((TObject*)(getCurr()->getItem()))->clear(cmode_a);
    }
  }

  // for RETAIN mode we are done, make no changes to topology
  //
  if (cmode_a == Integral::RETAIN) {
    return true;
  }

  // remove all of the vertices from the start and term vertices
  //
  start_vertex_d->removeAllArcsChild();
  term_vertex_d->removeAllArcsParent();
  
  // when the graph is in SYSTEM-allocation mode we need to iterate over
  // all vertices in the graph and delete the objects
  // 
  if ((getAllocationMode() == DstrBase::SYSTEM) || (cmode_a == Integral::FREE)) {
    for (boolean more = gotoFirst(); more;
	 more = gotoNext()) {

      // delete the TObject
      //
      delete getCurr()->getItem();

      // set the pointer to null
      //
      ((BiGraphVertex<TObject>*)getCurr())->setItem((TObject*)NULL);
    }
  }

  // clear the vertex list
  //
  DoubleLinkedList< BiGraphVertex<TObject> >::setAllocationMode(DstrBase::SYSTEM);
  DoubleLinkedList< BiGraphVertex<TObject> >::clear(Integral::RESET);
  DoubleLinkedList< BiGraphVertex<TObject> >::setAllocationMode(DstrBase::USER);

  // exit gracefully
  // 
  return true;
}

//---------------------------------------------------------------------------
//
// class-specific public methods:
//  insert/remove arc methods
//
//---------------------------------------------------------------------------

// method: insertArc
//
// arguments:
//  BiGraphVertex<TObject>* start_vertex: (input) the start vertex
//  BiGraphVertex<TObject>* end_vertex: (input) the ending vertex
//  boolean is_epsilon: (input) is the arc an epsilon transition
//  float weight: (input) the weight on the arc (if any)
//  
// return: a boolean value indicating status
//
// this method inserts an arc between two vertices in the graph provided
// that the two vertices are already present in the graph
//
template<class TObject>
boolean BiGraph<TObject>::insertArc(BiGraphVertex<TObject>* start_vertex_a,
				  BiGraphVertex<TObject>* end_vertex_a,
				  boolean is_epsilon_a, float weight_a) {

  
  // make sure neither vertex is NULL
  //
  if ((start_vertex_a == (BiGraphVertex<TObject>*)NULL) ||
      (end_vertex_a == (BiGraphVertex<TObject>*)NULL)) {
    return Error::handle(name(), L"insertArc", Error::NULL_ARG,
			 __FILE__, __LINE__);
  }
  
  // we don't allow connections between graphs so make sure the parent graph
  // for both vertices is either already this graph or is null
  //
  if (((start_vertex_a->getParentGraph() != (BiGraph<TObject>*)NULL) &&
       (start_vertex_a->getParentGraph() != this)) ||
      ((end_vertex_a->getParentGraph() != (BiGraph<TObject>*)NULL) &&
       (end_vertex_a->getParentGraph() != this))) {
    return Error::handle(name(), L"insertArc", ERR_MULPAR, __FILE__,
			 __LINE__);
  }
  
  // make sure that the start vertex and end vertex are already in the graph
  //
  if (start_vertex_a->getParentGraph() != this) {
    if ((start_vertex_a != start_vertex_d) &&
	(start_vertex_a != term_vertex_d)) {
      return Error::handle(name(), L"insertArc", Error::ARG,
			   __FILE__, __LINE__);
    }
  }
  if (end_vertex_a->getParentGraph() != this) {
    if ((end_vertex_a != start_vertex_d) &&
	(end_vertex_a != term_vertex_d)) {
      return Error::handle(name(), L"insertArc", Error::ARG,
			   __FILE__, __LINE__);
    }
  }
  
  // set the parent pointers regardless - this may be redundant but it is
  // probably no less efficient than putting an if statement around it
  //
  start_vertex_a->setParentGraph(this);
  end_vertex_a->setParentGraph(this);
  
  // add an arc from the start to the end in the child list
  //
  boolean return_val = start_vertex_a->insertArcChild(end_vertex_a, weight_a,
						      is_epsilon_a);

  // add an arc from the end to the start in the parent list
  //
  return_val &= end_vertex_a->insertArcParent(start_vertex_a, weight_a,
					      is_epsilon_a);  
  
  // exit gracefully
  //
  return return_val;
}

// method: removeArc
//
// arguments:
//  BiGraphVertex<TObject>* start_vertex: (input) the start vertex
//  BiGraphVertex<TObject>* end_vertex: (input) the ending vertex
//  
// return: a boolean value indicating status
//
// this method removes an arc between two vertices from the graph structure
//
template<class TObject>
boolean BiGraph<TObject>::removeArc(BiGraphVertex<TObject>* start_vertex_a,
				    BiGraphVertex<TObject>* end_vertex_a) {
  
  // make sure neither vertex is NULL
  //
  if ((start_vertex_a == (BiGraphVertex<TObject>*)NULL) ||
      (end_vertex_a == (BiGraphVertex<TObject>*)NULL)) {
    return Error::handle(name(), L"removeArc", Error::NULL_ARG,
			 __FILE__, __LINE__);
  }
  
  // we don't allow connections between graphs so make sure the parent graph
  // for both vertices is either already this graph or is null
  //
  if (((start_vertex_a->getParentGraph() != (BiGraph<TObject>*)NULL) &&
       (start_vertex_a->getParentGraph() != this)) ||
      ((end_vertex_a->getParentGraph() != (BiGraph<TObject>*)NULL) &&
       (end_vertex_a->getParentGraph() != this))) {
    return Error::handle(name(), L"removeArc", ERR_MULPAR, __FILE__,
			 __LINE__);
  }
  
  // make sure that the start vertex and end vertex are already in the graph
  //
  if (start_vertex_a->getParentGraph() != this) {
    if ((start_vertex_a != start_vertex_d) &&
	(start_vertex_a != term_vertex_d)) {
      return Error::handle(name(), L"removeArc", Error::ARG,
			   __FILE__, __LINE__);
    }
  }
  if (end_vertex_a->getParentGraph() != this) {
    if ((end_vertex_a != start_vertex_d) &&
	(end_vertex_a != term_vertex_d)) {
      return Error::handle(name(), L"removeArc", Error::ARG,
			   __FILE__, __LINE__);
    }
  }
  
  // remove the arc from the start to the end in the child list
  //
  boolean return_val = start_vertex_a->removeArcChild(end_vertex_a);

  // remove the arc from the end to the start in the parent list
  //
  return_val &= end_vertex_a->removeArcParent(start_vertex_a);

  // exit gracefully
  //
  return return_val;  
}

//---------------------------------------------------------------------------
//
// class-specific public methods:
//  graph property methods
//
//---------------------------------------------------------------------------

// method: get
//
// arguments:
//  DoubleLinkedList<TObject>& dlist: (input) graph vertex elements
//  DoubleLinkedList<TopoTriple>& alist: (input) graph topology
//  
// return: a boolean value indicating status
//
// this method reads the current graph structure into two lists and a
// hash table. The first list contains all data elements in the graph
// vertices and the second list contains information regarding the
// graph arcs.
//
template<class TObject>
boolean BiGraph<TObject>::get(DoubleLinkedList<TObject>& data_a,
			      DoubleLinkedList<TopoTriple>& arcs_a) const {

  // declare local variables and structures
  //
  Long index;
  Long src_pos;
  Long dst_pos;
  Float weight;
  Boolean epsilon(false);
  BiGraphArc<TObject>* arc;
  BiGraphVertex<TObject>* svert;
  BiGraphVertex<TObject>* dvert;
  HashTable<BiGVKey<TObject>, Long> khash;
 
  // initialize the index
  //
  index = 0;
  
  // iterate over all vertices in the graph
  //
  for (boolean more = const_cast<BiGraph<TObject>* >(this)->gotoFirst();
       more;
       more = const_cast<BiGraph<TObject>* >(this)->gotoNext()) {
    
    // add the vertex to the dlist
    //
    data_a.insert((TObject*)(const_cast<BiGraph<TObject>* >
			     (this)->getCurr()->getItem()));

    // associate a unique number with the vertex
    //
    BiGVKey<TObject> hashkey(this->getCurr());
    khash.insert(hashkey, &index);
    
    // increment the vertex index
    //
    index = (long)index + 1;
  }

  // iterate over all out going arcs from the start vertex
  //
  for (boolean arcs = const_cast<BiGraphVertex<TObject>* >
	 (start_vertex_d)->gotoFirstChild();
       arcs;
       arcs = const_cast<BiGraphVertex<TObject>* >
	 (start_vertex_d)->gotoNextChild()) {

    // retrieve the current arc
    //
    arc = start_vertex_d->getCurrChild();

    // get the arc weight
    //
    weight = arc->getWeight();
    
    // get the epsilon flag
    //
    epsilon = arc->getEpsilon();
    
    // get the destination vertex
    //
    dvert = arc->getVertex();
    
    // get the position of the source vertex
    //
    Long index(this->START_INDEX);
    src_pos = index;
    
    // get the position of the destination vertex
    //      
    if (dvert == this->getStart()) {
      Long index(this->START_INDEX);	
      dst_pos = index;
    }
    
    else if (dvert == this->getTerm()) {
      Long index(this->TERM_INDEX);
      dst_pos = index;
    }
    
    else {      
      BiGVKey<TObject> dst_key(dvert);      
      dst_pos = *(khash.get(dst_key));
    }
    
    // add the parameters to the alist
    //
    Pair<Long, Long> pair(src_pos, dst_pos);
    TopoTriple triple(pair, weight, epsilon);
    arcs_a.insert(&triple);    
  }
  
  // iterate over all vertices in the graph
  //
  for (boolean more = const_cast<BiGraph<TObject>* >(this)->gotoFirst();
       more;
       more = const_cast<BiGraph<TObject>* >(this)->gotoNext()) {

    // retrieve the current source vertex
    //
    svert = (BiGraphVertex<TObject>*)const_cast<BiGraph<TObject>* >
      (this)->getCurr();

    // iterate over all out going arcs in the current vertex
    //
    for (boolean arcs = svert->gotoFirstChild(); arcs;
	 arcs = svert->gotoNextChild()) {

      // retrieve the current arc
      //
      arc = svert->getCurrChild();

      // get the arc weight
      //
      weight = arc->getWeight();
      
      // get the epsilon flag
      //
      epsilon = arc->getEpsilon();
      
      // get the destination vertex
      //
      dvert = arc->getVertex();

      // get the position of the source vertex
      //
      if (svert == this->getStart()) {
	Long index(this->START_INDEX);
	src_pos = index;
      }

      else if (svert == this->getTerm()) {
	Long index(this->TERM_INDEX);
	src_pos = index;
      }
      
      else {
	BiGVKey<TObject> src_key(svert);
	src_pos = *(khash.get(src_key));
      }

      // get the position of the destination vertex
      //      
      if (dvert == this->getStart()) {
	Long index(this->START_INDEX);	
	dst_pos = index;
      }

      else if (dvert == this->getTerm()) {
	Long index(this->TERM_INDEX);
	dst_pos = index;
      }
      
      else {      
	BiGVKey<TObject> dst_key(dvert);      
	dst_pos = *(khash.get(dst_key));
      }

      // add the parameters to the arcs_a
      //
      Pair<Long, Long> pair(src_pos, dst_pos);
      TopoTriple triple(pair, weight, epsilon);
      arcs_a.insert(&triple);
    }
  }

  // iterate over all out going arcs from the destination vertex
  //  
  for (boolean arcs = term_vertex_d->gotoFirstChild(); arcs;
       arcs = term_vertex_d->gotoNextChild()) {

    // retrieve the current arc
    //
    arc = term_vertex_d->getCurrChild();

    // get the arc weight
    //
    weight = arc->getWeight();
    
    // get the epsilon flag
    //
    epsilon = arc->getEpsilon();
    
    // get the destination vertex
    //
    dvert = arc->getVertex();

    // get the position of the source vertex
    //
    Long index(this->TERM_INDEX);
    src_pos = index;
    
    // get the position of the destination vertex
    //      
    if (dvert == this->getStart()) {
      Long index(this->START_INDEX);	
      dst_pos = index;
    }
    
    else if (dvert == this->getTerm()) {
      Long index(this->TERM_INDEX);
      dst_pos = index;
    }
    
    else {      
      BiGVKey<TObject> dst_key(dvert);      
      dst_pos = *(khash.get(dst_key));
    }

    // add the parameters to the alist
    //
    Pair<Long, Long> pair(src_pos, dst_pos);
    Triple<Pair<Long, Long>, Float, Boolean> triple(pair, weight, epsilon);
    arcs_a.insert(&triple);        
  }
  
  // exit gracefully
  //
  return true;
}

//---------------------------------------------------------------------------
//
// class-specific public methods:
//  insert/remove vertex methods
//
//---------------------------------------------------------------------------

// method: insertVertex
//
// arguments:
//  TObject* obj: (input) object to insert
//  
// return: a BiGraphVertex<TObject>* containing the objected to be added
//
// this method inserts a new object on the graph and return the created
// vertex
//
template<class TObject>
BiGraphVertex<TObject>* BiGraph<TObject>::insertVertex(TObject* obj_a) {

  // perform error checking
  //
  if (obj_a == (TObject*)NULL) {
    Error::handle(name(), L"insertVertex", Error::ARG,
		  __FILE__, __LINE__);
    return (BiGraphVertex<TObject>*)NULL;
  }
    
  // declare local variables and allocate a new BiGraphVertex object
  //
  TObject* new_obj = (TObject*)NULL;
  BiGraphVertex<TObject>* v = new BiGraphVertex<TObject>();

  // when the graph is in SYSTEM mode make a copy of the object
  //
  if (getAllocationMode() == DstrBase::SYSTEM) {
    new_obj = new TObject(*obj_a);
  }

  // assign the object pointer when in USER mode
  //
  else {
    new_obj = obj_a;
  }

  // set the object on vertex
  //
  v->setItem(new_obj);

  // set the parent graph for the vertex
  //
  v->setParentGraph(this);

  // add vertex on the graph
  //
  DoubleLinkedList< BiGraphVertex<TObject> >::insert(v);
  
  // exit gracefully
  //
  return v;
}

// method: removeVertex
//
// arguments:
//  
// return: a BiGraphVertex<TObject>* containing the objected to be added
//
// this method removes the current vertex from the graph
//
template<class TObject>
boolean BiGraph<TObject>::removeVertex() {

  // declare local variables
  //
  BiGraphVertex<TObject>* vertex;
  
  // make sure the current vertex is valid
  //
  if (this->getCurr() == (BiGraphVertex<TObject>*)NULL) {
    return Error::handle(name(), L"removeVertex", Error::ARG,
			 __FILE__, __LINE__);
  }

  // get the current vertex
  //
  BiGraphVertex<TObject>* this_vertex =
    (BiGraphVertex<TObject>*)this->getCurr();

  // when in SYSTEM mode make a copy of the object data
  //
  if (getAllocationMode() == DstrBase::SYSTEM) {
    delete this_vertex->getItem();
  }  

  // save the current state so we can return to the node after we
  // search for other arcs.
  //
  this->setMark();

  // if the start vertex points to the vertex we are about to delete
  // then remove the arc
  //
  BiGraphVertex<TObject>* start_vertex = this->getStart();

  for (boolean morea = start_vertex->gotoFirstChild(); morea;
       morea = start_vertex->gotoNextChild()) {
    if (this_vertex == start_vertex->getCurrChild()->getVertex()) {
      start_vertex->removeArcChild();     
    }
  }

  for (boolean morea = start_vertex->gotoFirstParent(); morea;
       morea = start_vertex->gotoNextParent()) {
    if (this_vertex == start_vertex->getCurrParent()->getVertex()) {
      start_vertex->removeArcParent();     
    }
  }  

  // if the term vertex points to the vertex we are about to delete
  // then remove the arc
  //
  BiGraphVertex<TObject>* term_vertex = this->getTerm();  

  for (boolean morea = term_vertex->gotoFirstChild(); morea;
       morea = term_vertex->gotoNextChild()) {
    if (this_vertex == term_vertex->getCurrChild()->getVertex()) {
      term_vertex->removeArcChild();      
    }
  }

  for (boolean morea = term_vertex->gotoFirstParent(); morea;
       morea = term_vertex->gotoNextParent()) {
    if (this_vertex == term_vertex->getCurrParent()->getVertex()) {
      term_vertex->removeArcParent();      
    }
  }    
    
  // now search for all arcs comming into this vertex so they can be
  // removed.
  //
  for (boolean moren = gotoFirst(); moren; moren = gotoNext()) {

    // retrieve the current vertex
    //
    vertex = (BiGraphVertex<TObject>*)this->getCurr();
    
    // iterate over all arcs in the current vertex
    //
    for (boolean morea = vertex->gotoFirstChild(); morea;
	 morea = vertex->gotoNextChild()) {

      // if the destination vertex is the same as the vertex we are
      // about to delete then remove the arc
      //
      if (this_vertex == vertex->getCurrChild()->getVertex()) {
	vertex->removeArcChild();
      }
    }

    // iterate over all arcs in the current vertex
    //
    for (boolean morea = vertex->gotoFirstParent(); morea;
	 morea = vertex->gotoNextParent()) {

      // if the destination vertex is the same as the vertex we are
      // about to delete then remove the arc
      //
      if (this_vertex == vertex->getCurrParent()->getVertex()) {
	vertex->removeArcParent();
      }
    }    
  }

  // restore the previous state. this is the vertex we will actually
  // remove.
  //
  this->gotoMark();
    
  // remove the object from the linked list
  //
  boolean status = false;
  BiGraphVertex<TObject>* graph_vertex;
  status = DoubleLinkedList< BiGraphVertex<TObject> >::remove(graph_vertex);
  delete graph_vertex;

  return status;
}

// method: removeVertex
//
// arguments:
//  TObject* obj: (input) object to remove
//  
// return: a BiGraphVertex<TObject>* containing the objected to be added
//
// this method removes the current vertex from the graph and
// outputs the objects contained in the deleted vertex
//
template<class TObject>
boolean BiGraph<TObject>::removeVertex(TObject*& obj_a) {

  // make sure the current vertex is valid
  //
  if (this->getCurr() == (BiGraphVertex<TObject>*)NULL) {
    return Error::handle(name(), L"removeVertex", Error::ARG,
			 __FILE__, __LINE__);
  }
  
  // make sure memory is allocated if we are SYSTEM-allocated
  //
  if ((alloc_d == DstrBase::SYSTEM) && (obj_a == (TObject*)NULL)) {
    return (Error::handle(name(), L"removeVertex", Error::NULL_ARG,
			  __FILE__, __LINE__));
  }

  // get the current vertex
  //
  BiGraphVertex<TObject>* this_vertex =
    (BiGraphVertex<TObject>*)this->getCurr();

  // when in SYSTEM mode make a copy of the object data
  //
  if (getAllocationMode() == DstrBase::SYSTEM) {
    obj_a->assign(*this_vertex->getItem());
    delete this_vertex->getItem();
  }

  // when in USER mode assign the object pointer
  //
  else {
    obj_a = this_vertex->getItem();
  }
  
  // remove all outgoing arcs from the vertex
  //
  this_vertex->removeAllArcsChild();

  // save the current state so we can return to the node after we
  // search for other arcs.
  //
  this->setMark();

  // if the start vertex points to the vertex we are about to delete
  // then remove the arc
  //
  BiGraphVertex<TObject>* start_vertex = this->getStart();

  for (boolean morea = start_vertex->gotoFirstChild(); morea;
       morea = start_vertex->gotoNextChild()) {
    if (this_vertex == start_vertex->getCurrChild()->getVertex()) {
      start_vertex->removeArcChild();     
    }
  }

  for (boolean morea = start_vertex->gotoFirstParent(); morea;
       morea = start_vertex->gotoNextParent()) {
    if (this_vertex == start_vertex->getCurrParent()->getVertex()) {
      start_vertex->removeArcParent();     
    }
  }  

  // if the term vertex points to the vertex we are about to delete
  // then remove the arc
  //
  BiGraphVertex<TObject>* term_vertex = this->getTerm();  
  for (boolean morea = term_vertex->gotoFirstChild(); morea;
       morea = term_vertex->gotoNextChild()) {
    if (this_vertex == term_vertex->getCurrChild()->getVertex()) {
      term_vertex->removeArcChild();      
    }
  }

  for (boolean morea = term_vertex->gotoFirstParent(); morea;
       morea = term_vertex->gotoNextParent()) {
    if (this_vertex == term_vertex->getCurrParent()->getVertex()) {
      term_vertex->removeArcParent();      
    }
  }  
  
  // now search for all arcs comming into this vertex so they can be
  // removed.
  //
  for (boolean moren = gotoFirst(); moren; moren = gotoNext()) {

    // retrieve the current vertex
    //
    BiGraphVertex<TObject>* vertex = (BiGraphVertex<TObject>*)this->getCurr();

    // iterate over all arcs in the current vertex
    //
    for (boolean morea = vertex->gotoFirstChild(); morea;
	 morea = vertex->gotoNextChild()) {

      // if the destination vertex is the same as the vertex we are
      // about to delete then remove the arc
      //
      if (this_vertex == vertex->getCurrChild()->getVertex()) {
	vertex->removeArcChild();
      }
    }

    // iterate over all arcs in the current vertex
    //
    for (boolean morea = vertex->gotoFirstParent(); morea;
	 morea = vertex->gotoNextParent()) {

      // if the destination vertex is the same as the vertex we are
      // about to delete then remove the arc
      //
      if (this_vertex == vertex->getCurrParent()->getVertex()) {
	vertex->removeArcParent();
      }
    }    
  }

  // restore the previous state. this is the vertex we will actually
  // remove.
  //
  this->gotoMark();
    
  // remove the object from the linked list
  //
  boolean status = false;
  BiGraphVertex<TObject>* graph_vertex;
  status = DoubleLinkedList< BiGraphVertex<TObject> >::remove(graph_vertex);
  delete graph_vertex;

  return status;
}

// method: contains
//
// arguments:
//  TObject* obj: (input) the object to be found
//
// return: a boolean value indicating status
//
// this method determines if the input object is in the list of vertices
//
template<class TObject>
boolean BiGraph<TObject>::contains(const TObject* obj_a) const {

  // check if the input object is NULL
  //
  if (obj_a == (TObject*)NULL) {
    return Error::handle(name(), L"contains", Error::NULL_ARG, __FILE__,
			 __LINE__);
  }

  // save the current position
  //
  const_cast<BiGraph<TObject>* >(this)->setMark();

  // temporary variables
  //
  boolean obj_found = false;
  boolean more_nodes = const_cast<BiGraph<TObject>* >(this)->gotoFirst();
  
  // search from the beginning for the item
  //
  while (!obj_found && more_nodes) {

    // get the current vertex
    //
    BiGraphVertex<TObject>* this_vertex = (BiGraphVertex<TObject>*)
      const_cast<BiGraph<TObject>* >(this)->getCurr();

    // get the object contained in the vertex
    //
    TObject* this_obj = this_vertex->getItem();
    
    // compare the objects for equality
    //
    obj_found = obj_a->eq(*this_obj);
    
    if (!obj_found) {
      more_nodes = const_cast<BiGraph<TObject>* >(this)->gotoNext();
    }
  }

  // restore the previous position
  //
  const_cast<BiGraph<TObject>* >(this)->gotoMark();
  
  // return whether or not the object was found
  //
  return obj_found;  
}

// method: contains
//
// arguments:
//  BiGraphVertex<TObject>* vertex: (input) the vertex to be found
//
// return: a boolean value indicating status
//
// this method determines if the input vertex is in the list of
// vertices. note that we can't just call list contains since we only
// want to compare pointers, not values.
//
template<class TObject>
boolean
BiGraph<TObject>::contains(const BiGraphVertex<TObject>* vertex_a) const {

  // check if the input object is NULL
  //
  if (vertex_a == (BiGraphVertex<TObject>*)NULL) {
    return Error::handle(name(), L"contains", Error::NULL_ARG,
			 __FILE__, __LINE__);
  }

  // save the current position
  //
  const_cast<BiGraph<TObject>* >(this)->setMark();

  // temporary variables
  //
  boolean vertex_found = false;
  boolean more_nodes = const_cast<BiGraph<TObject>* >(this)->gotoFirst();
  
  // search from the beginning for the item
  //
  while (!vertex_found && more_nodes) {

    // get the current vertex
    //
    BiGraphVertex<TObject>* this_vertex = (BiGraphVertex<TObject>*)
      const_cast<BiGraph<TObject>* >(this)->getCurr();

    // compare the pointers for equality
    //
    if (this_vertex == vertex_a) {
      vertex_found = true;
    }
    
    if (!vertex_found) {
      more_nodes = const_cast<BiGraph<TObject>* >(this)->gotoNext();
    }
  }

  // restore the previous state
  //
  const_cast<BiGraph<TObject>* >(this)->gotoMark();
  
  // return whether or not the object was found
  //
  return vertex_found;
}

// method: find
//
// arguments:
//  TObject* obj: (input) the object to be found
//
// return: a boolean value indicating status
//
// this method determines if the input object is in the list of vertices
// and moves the list current pointer to the first occurance of the object
//
template<class TObject>
boolean BiGraph<TObject>::find(const TObject* obj_a) {

  // check if the input object is NULL
  //
  if (obj_a == (TObject*)NULL) {
    return Error::handle(name(), L"find", Error::NULL_ARG, __FILE__, __LINE__);
  }

  // save the current position
  //
  this->setMark();

  // temporary variables
  //
  boolean obj_found = false;
  boolean more_nodes = this->gotoFirst();
  
  // search from the beginning for the item
  //
  while (!obj_found && more_nodes) {

    // get the current vertex
    //
    BiGraphVertex<TObject>* this_vertex =
      (BiGraphVertex<TObject>*)this->getCurr();

    // get the object contained in the vertex
    //
    TObject* this_obj = this_vertex->getItem();
    
    // compare the objects for equality
    //
    obj_found = obj_a->eq(*this_obj);
    
    if (!obj_found) {
      more_nodes = this->gotoNext();
    }
  }

  if (!obj_found) {
    this->gotoMark();
  }
  
  // return whether or not the object was found
  //
  return obj_found;
}

// method: find
//
// arguments:
//  BiGraphVertex<TObject>* vertex: (input) the vertex to be found
//
// return: a boolean value indicating status
//
// this method determines if the input vertex is in the list of
// vertices. if found the vertex list is left pointing to the
// vertex. note that we can't just call list contains since we only
// want to compare pointers, not values.
//
template<class TObject>
boolean BiGraph<TObject>::find(const BiGraphVertex<TObject>* vertex_a) {

  // check if the input object is NULL
  //
  if (vertex_a == (BiGraphVertex<TObject>*)NULL) {
    return Error::handle(name(), L"find", Error::NULL_ARG,
			 __FILE__, __LINE__);
  }

  // save the current position
  //
  const_cast<BiGraph<TObject>* >(this)->setMark();

  // temporary variables
  //
  boolean vertex_found = false;
  boolean more_nodes = const_cast<BiGraph<TObject>* >(this)->gotoFirst();
  
  // search from the beginning for the item
  //
  while (!vertex_found && more_nodes) {

    // get the current vertex
    //
    BiGraphVertex<TObject>* this_vertex = (BiGraphVertex<TObject>*)
      const_cast<BiGraph<TObject>* >(this)->getCurr();

    // compare the pointers for equality
    //
    if (this_vertex == vertex_a) {
      vertex_found = true;
    }
    
    if (!vertex_found) {
      more_nodes = const_cast<BiGraph<TObject>* >(this)->gotoNext();
    }
  }

  // restore the previous state if the vertex was not found
  //
  if (!vertex_found) {
    const_cast<BiGraph<TObject>* >(this)->gotoMark();
  }
  
  // return whether or not the object was found
  //
  return vertex_found;
}


//---------------------------------------------------------------------------
//
// class-specific public methods:
//  graph ordering methods
//
//---------------------------------------------------------------------------   
// method: topologicalSort
//
// arguments:
//  DoubleLinkedList<TObject>& output: (output) sorted list of data
//  boolean partial: (input) sort only white nodes?
//
// return: a boolean value indicating status
//
// this method performs a topological sort on the graph and returns
// a sorted list of the data. the algorithm, "topological sort" using depth
// first search, is taken from:
// 
//  T. Cormen, C. Leiserson, R. Rivest, "Exercise 23.4-1",
//  Introduction to Algorithms, MIT Press, Boston, Massachusetts, USA,
//  pp. 485, 1998.
//
// Modified to allow the graph to not be fully
// initialized to WHITE upon initialization so partial graphs can be analyzed.
//
template<class TObject>
boolean BiGraph<TObject>::topologicalSort(DoubleLinkedList<TObject>& output_a,
					  boolean partial_a) {

  // make sure the color hash table is allocated
  //
  if (chash_d == (BiColorHash<TObject>*)NULL) {
    Error::handle(name(), L"topologicalSort", Error::ARG, __FILE__, __LINE__);
    return false;
  }
  
  // if partial is true, then we assume that the color of all graph
  // nodes is already set as the user wants. this means that only
  // nodes that start out white will make it onto the output list. if
  // partial is false it performs a full search, hence it must
  // initialize all nodes to white.
  //
  if (!partial_a) {
    setColor(Integral::WHITE);
  }

  // run a depth first search that builds the list as it visits each
  // node.
  //
  for (boolean more = gotoFirst(); more; more = gotoNext()) {
    BiGraphVertex<TObject>* vertex = (BiGraphVertex<TObject>*)getCurr();
    if (getColor(vertex) == Integral::WHITE) {
      dfsVisitTS(output_a, *vertex);
    }
  }

  // exit gracefully
  //
  return true;
}

//---------------------------------------------------------------------------
//
// class-specific public methods:
//  graph color methods
//
//---------------------------------------------------------------------------

// method: invertColor
//
// arguments: none
//
// return: a boolean value indicating status
//
// invert the color of all nodes. any black node will become white,
// any non-black node will become black. this is useful for pruning
// before topological sorts.
//
template<class TObject>
boolean BiGraph<TObject>::invertColor() {

  // make sure the color hash table is allocated
  //
  if (chash_d == (BiColorHash<TObject>*)NULL) {
    Error::handle(name(), L"invertColor", Error::ARG, __FILE__, __LINE__);
    return false;
  }
  
  // loop through all vertices
  //
  for (boolean more_nodes = gotoFirst();
       more_nodes;
       more_nodes = gotoNext()) {

    if (chash_d->get(getCurr()) == Integral::BLACK) {
      chash_d->set(getCurr(), Integral::WHITE);
    }
    else {
      chash_d->set(getCurr(), Integral::BLACK);
    }
  }

  // exit gracefully
  //
  return true;
}

// method: makeColorHash
//
// arguments:
//  Integral::COLOR col: (input) color to initialize the vertices
//
// return: a boolean value indicating status
//
// this method creates a hash table of vertices and initializes all
// vertices in the hash table to the given color 
//
template<class TObject>
boolean BiGraph<TObject>::makeColorHash(Integral::COLOR col_a) {

  // allocate memory to the color hash table
  //
  if (chash_d != (BiColorHash<TObject>*)NULL) {
    Error::handle(name(), L"makeColorHash", Error::ARG, __FILE__, __LINE__);
    return false;
  }
  else {
    chash_d = new BiColorHash<TObject>();
  }
  
  // iterate over all vertices in the graph
  //
  for (boolean more_nodes = gotoFirst();
       more_nodes;
       more_nodes = gotoNext()) {

    chash_d->insert(getCurr(), col_a);
  }

  // add the start and term vertices
  //
  chash_d->insert(getStart(), col_a);
  chash_d->insert(getTerm(), col_a);

  // exit gracefully
  //
  return true;
}

// method: setColor
//
// arguments:
//  Integral::COLOR col: (input) color to initialize the vertices
//
// return: a boolean value indicating status
//
// this method sets all vertices to a specified color
//
template<class TObject>
boolean BiGraph<TObject>::setColor(Integral::COLOR col_a) {

  // make sure the color hash table is allocated
  //
  if (chash_d == (BiColorHash<TObject>*)NULL) {
    Error::handle(name(), L"setColor", Error::ARG, __FILE__, __LINE__);
    return false;
  }
  
  // loop over all vertices
  //
  for (boolean more_nodes = gotoFirst();
       more_nodes;
       more_nodes = gotoNext()) {

    // set the color
    //
    chash_d->set(getCurr(), col_a);
  }

  // exit gracefully
  //
  return true;
}

// method: isColor
//
// arguments:
//  Integral::COLOR col: (input) color we match against
//
// return: a boolean value indicating status
//
// are all nodes the specified color?
//
template<class TObject>
boolean BiGraph<TObject>::isColor(Integral::COLOR col_a) {

  // make sure the color hash table is allocated
  //
  if (chash_d == (BiColorHash<TObject>*)NULL) {
    Error::handle(name(), L"isColor", Error::ARG, __FILE__, __LINE__);
    return false;
  }
  
  // loop over all vertices
  //
  for (boolean more = gotoFirst(); more; more = gotoNext()) {

    // get the color and compare
    //
    if (chash_d->get(getCurr()) != col_a) {
      return false;
    }
  }
  
  // exit gracefully
  //
  return true;
}

// method: replaceColor
//
// arguments:
//  BiGraphVertex::COLOR old_col: (input) old color to search for
//  BiGraphVertex::COLOR new_col: (input) new color to assign to
//
// return: a boolean value indicating status
//
// change the color of all nodes that are currently old_col to new_col.
//
template<class TObject>
boolean BiGraph<TObject>::replaceColor(Integral::COLOR old_col_a,
				     Integral::COLOR new_col_a) {

  // make sure the color hash table is allocated
  //
  if (chash_d == (BiColorHash<TObject>*)NULL) {
    Error::handle(name(), L"replaceColor", Error::ARG, __FILE__, __LINE__);
    return false;
  }
  
  // loop through all vertices
  //
  for (boolean more = gotoFirst(); more; more = gotoNext()) {

    // if this node is old_color, set it to new_color
    //
    if (chash_d->get(getCurr()) == old_col_a) {
      chash_d->set(getCurr(), new_col_a);
    }
  }

  // exit gracefully
  //
  return true;
}

// method: dfsVisitTS
//
// arguments:
//  DoubleLinkedList<TObject>& output: (output) sorted list of data
//  BiGraphVertex<TObject>& v: (input) vertex to visit
//
// return: a boolean value indicating status
//
// this method performs a topological sort on the graph and returns
// a sorted list of the data. 
//
template<class TObject>
boolean BiGraph<TObject>::dfsVisitTS(DoubleLinkedList<TObject>& output_a,
				   BiGraphVertex<TObject>& v_a) {

  // set the color of this vertex to gray
  //
  setColor(&v_a, Integral::GREY);

  // loop over all arcs starting from this node
  //
  for (boolean more = v_a.gotoFirstChild(); more;
       more = v_a.gotoNextChild()) {

    // if this vertex is white, expand it
    //
    BiGraphVertex<TObject>* vertex = v_a.getCurrChild()->getVertex();
    if (getColor(vertex) == Integral::WHITE) {
      dfsVisitTS(output_a, *vertex);
    }
  }

  // since all arcs have been visited, set vertex to black
  //
  chash_d->set(&v_a, Integral::BLACK);

  // prepend the data to the output list
  //
  output_a.insertFirst(v_a.getItem());

  // exit gracefully
  //
  return true;
}

// method: dfsVisit
//
// arguments:
//  BiGraphVertex<TObject>& v: (input) vertex to visit
//
// return: a boolean value indicating status
//
// "depth-first-search" taken from:
// 
//  T. Cormen, C. Leiserson, R. Rivest, "Exercise 23.4-1",
//  Introduction to Algorithms, MIT Press, Boston, Massachusetts, USA,
//  pp. 478, 1998.
//
// this method performs a depth-first-search visit for this node. it
// leaves the graph with all nodes reachable from the starting node
// colored black.
//
template<class TObject>
boolean BiGraph<TObject>::dfsVisit(BiGraphVertex<TObject>& v_a) {
  
  // set the color of this vertex to grey
  //
  setColor(&v_a, Integral::GREY);

  // loop over all arcs starting from this node
  //
  for (boolean more = v_a.gotoFirst(); more; more = v_a.gotoNext()) {

    // if the node is white it has not been seen before, so
    // recursively call the dfsVisit method.
    //
    BiGraphVertex<TObject>* vertex = v_a.getCurr()->getVertex();
    if (chash_d->get(vertex) == Integral::WHITE) {
      dfsVisit(*vertex);
    }
  }

  // since all arcs have been visited, set vertex to black
  //
  chash_d->set(&v_a, Integral::BLACK);

  // exit gracefully
  //
  return true;
}

// method: readVertexDataText
//
// arguments:
//  Sof& sof: (input) sof file
//  SofParser parser: (input) sof parser
//  ReadHash hash_table: (input) mapping between vertex and index position
//
// return: a boolean value indicating status
//
// write out the vertex data for this graph
//
template<class TObject>
boolean BiGraph<TObject>::readVertexDataText(Sof& sof_a, SofParser& parser_a,
					     ReadHash& hash_table_a) const {


  // declare local variables
  //
  String index_str;
  
  // the following is an example of the format used to write the
  // graph vertices to file in text mode.
  //
  // vertices =
  //    {0, {\a}},
  //	{1, {\b}},
  //	{2, {\c}},
  //	{3, {\d}},
  //	{4, {\e}},
  //	{5, {\f}},
  //	{6, {\g}};
  //
  // read the vertices
  //
  Long new_size((long)0);
  
  // read the number of 'vertices' in the above example we have
  // seven vertices in the graph
  //
  new_size = parser_a.countTokens(PARAM_VERTICES);

  if (debug_level_d >= Integral::DETAILED) {
    new_size.debug(L"reading this many vertices");
  }

  // insert the start vertex into the hash table
  //
  index_str.assign(START_INDEX);
  hash_table_a.insert(index_str, this->getStart());

  // insert the term vertex into the hash table
  //
  index_str.assign(TERM_INDEX);
  hash_table_a.insert(index_str, this->getTerm());    

  // read the vertices one node at a time
  //
  for (long i = 0; i < new_size; i++) {

    // we need a sub-parser to read the vertices.
    //
    SofParser sub_parser;
    sub_parser.setNest();
    sub_parser.setImplicitParam();

    sub_parser.load(sof_a, parser_a.getEntry(sof_a, PARAM_VERTICES, i, 1));

    // first read the index number
    //
    sub_parser.setNest();
    sub_parser.setImplicitParam();

    // read the number of tokens in the vertex entry i.e., in the
    // example above the entry: {0, {\a}}, has two tokens
    //
    long num_tok = sub_parser.countTokens(parser_a.implicitPname());
    
    if (num_tok != 2) {
      return Error::handle(name(), L"readVertexDataText", Error::READ,
			   __FILE__, __LINE__, Error::WARNING);
    }

    // read the position of the vertex in the underlying list
    // container i.e., in the example above the entry: {0, {\a}},
    // has a position of '0' in the list container class
    //    
    if (!sub_parser.read(index_str, sof_a,
	   sub_parser.getEntry(sof_a, parser_a.implicitPname(), 0, 1))) {
      return false;
    }
    
    if (debug_level_d >= Integral::ALL) {
      index_str.debug(L"read this index");
    }

    sub_parser.setNest();
    sub_parser.setImplicitParam();

    // read the number of bytes of the vertex data element i.e.,
    // in the example above we read the number of bytes of: '\a'
    //
    long vertex_len = sub_parser.getEntry(sof_a,
					  parser_a.implicitPname(), 1, 1);

    // read the vertex data. note that we build the vertex by hand
    // instead of calling insertVertex since we always have to
    // allocate the memory. if we called insertVertex there might be
    // a wasted copy if the graph was in SYSTEM mode.
    //
    BiGraphVertex<TObject>* v = new BiGraphVertex<TObject>();
    v->setParentGraph(const_cast<BiGraph<TObject>* >(this));
    TObject* data = new TObject;

    // read the vertex data element .i.e., in the example above
    // we read the character 'a' in: {0, {\a}},
    //
    if (!data->readData(sof_a, DEF_PARAM, vertex_len, false, true)) {
      return Error::handle(name(), L"readVertexDataText", Error::READ,
			     __FILE__, __LINE__, Error::WARNING);
    }

    // set the data element of the vertex we created to the one
    // that we parsed from the text file
    //
    v->setItem(data);
    if (debug_level_d >= Integral::ALL) {
      v->debug(L"read this vertex");
    }
    
    // add the vertex to the graph
    //
    const_cast<BiGraph<TObject>* >
      (this)->DoubleLinkedList< BiGraphVertex<TObject> >::insert(v);
    
    // place the vertex onto a HashTable for reference
    //
    hash_table_a.insert(index_str, v);
  }
  
  // exit gracefully
  //
  return true;
}

// method: readArcDataText
//
// arguments:
//  Sof& sof: (input) sof file
//  SofParser parser: (input) sof parser
//  ReadHash& hash_table: (input) mapping between vertex and index
//
// return: a boolean value indicating status
//
// read in the arc data for this graph
//
template<class TObject>
boolean BiGraph<TObject>::readArcDataText(Sof& sof_a, SofParser& parser_a,
					  ReadHash& hash_table_a) const {

  // declare local variables
  //
  String index_str;  

  // the following is an example of the format used to write the
  // graph arcs to file in text mode. where 'S' and 'T' denote
  // the start and term nodes and 'true' denotes a epsilon
  // transition between the vertices and '{0, 3, 0.7}' denotes
  // an arc from vertex '0' to vertex '3' with a weight of '0.7'
  //
  // arcs =
  //	{S, 0, 0, true},
  //	{S, 1, 0, true},
  //	{S, 2, 0, true},
  //	{0, 3, 0.7},
  //	{0, 5, 0.8},
  //	{1, 3, 0.9},
  //	{2, 3, 3.7},
  //	{2, 6, 3.1},
  //	{3, 4, 1.2},
  //	{4, 5, 6.1},
  //	{4, 6, 1.7},
  //	{5, T, 0, true},
  //	{6, T, 0, true};
  //
  
  // read the arcs
  //
  Long num_arcs((long)0);
  
  // read the number of arcs entries i.e., in the example above we have
  // thirteen arc between vertices in the graph entries.
  //
  num_arcs = parser_a.countTokens(PARAM_ARCS);

  if (debug_level_d >= Integral::ALL) {
    num_arcs.debug(L"about to read this many arcs");
  }

  // now read the arcs
  //
  for (long i = 0; i < num_arcs; i++) {

    // we need a sub-parser to read in the arc list.
    //
    SofParser sub_parser;
    sub_parser.setNest();
    sub_parser.setImplicitParam();

    sub_parser.load(sof_a, parser_a.getEntry(sof_a, PARAM_ARCS, i, 1));

    // determine the number in the arc entry i.e., in the example above
    // we have the entries either three or four tokens i.e.,
    // {0, 3, 0.7} or {S, 0, 0, true}
    //
    Byte ntokens;
    ntokens = sub_parser.countTokens(parser_a.implicitPname());

    if (debug_level_d >= Integral::DETAILED) {
      ntokens.debug(L"reading this many tokens on this arc");
    }

    // each arc can have 3 to 4 tokens:
    //    1) a source node index,
    //    2) a destination node index
    //    3) the weight (for a weighted graph)
    //    4) a flag for if this is an epsilon transition or not
    //
    if ((byte)ntokens < 2) {
      if (debug_level_d >= Integral::ALL) {
	Console::put(L"not enough elements in arc list");
      }
      return Error::handle(name(), L"readArcDataText", Error::READ,
			   __FILE__, __LINE__, Error::WARNING);
    }

    // weighted graphs needs at least 3 tokens
    //
    if (isWeighted() && ((byte)ntokens < 3)) {
      if (debug_level_d >= Integral::ALL) {
	Console::put(L"not enough elements in weighted arc list");
      }
      return Error::handle(name(), L"readArcDataText", Error::READ,
			   __FILE__, __LINE__, Error::WARNING);
    }
    
    // prepare to read the source and destination indices
    //
    sub_parser.setNest();
    sub_parser.setImplicitParam();

    String source;
    String dest;
    
    // first read the source index
    //
    if (!sub_parser.read(index_str, sof_a,
	 sub_parser.getEntry(sof_a, parser_a.implicitPname(), 0, 1))) {
      return Error::handle(name(), L"readArcDataText", Error::READ,
			   __FILE__, __LINE__, Error::WARNING);
    }

    // check if the source index is the start vertex
    //
    if (index_str.eq(START_NAME)) {
      if (!source.assign(START_INDEX)) {
	return Error::handle(name(), L"readArcDataText", Error::READ,
			     __FILE__, __LINE__, Error::WARNING);
      }
    }
	
    // check if the source index is the term vertex
    //
    else if (index_str.eq(TERM_NAME)) {
      if (!source.assign(TERM_INDEX)) {
	return Error::handle(name(), L"readArcDataText", Error::READ,
			     __FILE__, __LINE__, Error::WARNING);
      }
    }

    else {
      if (!source.assign(index_str)) {
	return Error::handle(name(), L"readArcDataText", Error::READ,
			     __FILE__, __LINE__, Error::WARNING);
      }
    }

    if (debug_level_d >= Integral::ALL) {
      source.debug(L"read this source node");
    }

    // now read the destination index
    //
    String index_str;
    
    if (!sub_parser.read(index_str, sof_a,
	 sub_parser.getEntry(sof_a, parser_a.implicitPname(), 1, 1))) {
      return Error::handle(name(), L"readArcDataText", Error::READ,
			   __FILE__, __LINE__, Error::WARNING);
    }
    
    // check if the destination index is the start vertex
    //
    if (index_str.eq(START_NAME)) {
      if (!dest.assign(START_INDEX)) {
	return Error::handle(name(), L"readArcDataText", Error::READ,
			     __FILE__, __LINE__, Error::WARNING);
      }
    }
	
    // check if the destination index is the term vertex
    //
    else if (index_str.eq(TERM_NAME)) {
      if (!dest.assign(TERM_INDEX)) {
	return Error::handle(name(), L"readArcDataText", Error::READ,
			     __FILE__, __LINE__, Error::WARNING);
      }
    }

    else {
      if (!dest.assign(index_str)) {
	return Error::handle(name(), L"readArcDataText", Error::READ,
			     __FILE__, __LINE__, Error::WARNING);
      }
    }

    if (debug_level_d >= Integral::ALL) {
      dest.debug(L"read this dest node");
    }
    ntokens -= 2;
    
    Float weight(0.0);
    
    // if this is a weighted graph, the third token for each arc is the
    // weight
    //
    if (isWeighted()) {

      String weight_str;
      if (!sub_parser.read(weight_str, sof_a,
	   sub_parser.getEntry(sof_a, parser_a.implicitPname(), 2, 1))) {
	return Error::handle(name(), L"readArcDataText", Error::READ,
			     __FILE__, __LINE__, Error::WARNING);
      }
      
      if (!weight.assign(weight_str)) {
	return Error::handle(name(), L"readArcDataText", Error::READ,
			     __FILE__, __LINE__, Error::WARNING);
      }
      
      if (debug_level_d >= Integral::ALL) {
	weight.debug(L"read this weight");
      }
      ntokens -= 1;
    }      

    // if there are any tokens left, an epsilon flag must be specified
    //
    Boolean is_epsilon(false);
    if ((byte)ntokens > 0) {
      String eps_str;
      if (!sub_parser.read(eps_str, sof_a,
	  sub_parser.getEntry(sof_a, parser_a.implicitPname(), 3, 1))) {
	return Error::handle(name(), L"readArcDataText", Error::READ,
			     __FILE__, __LINE__, Error::WARNING);
      }
      
      if (!is_epsilon.assign(eps_str)) {
	return Error::handle(name(), L"readArcDataText", Error::READ,
			     __FILE__, __LINE__, Error::WARNING);
      }
      ntokens -= 1;
    }

    // check for leftover tokens: report error if found
    //
    if ((byte)ntokens != 0) {
      Console::put(L"leftover tokens on arc");
      return Error::handle(name(), L"readArcDataText", Error::READ,
			   __FILE__, __LINE__, Error::WARNING);
    }

    // insert the arc from start to finish
    //
    if (isWeighted()) {

      // get the source and destination vertices
      //
      BiGraphVertex<TObject>* src_vertex = hash_table_a.get(source);
      BiGraphVertex<TObject>* dst_vertex = hash_table_a.get(dest);

      // make sure that the source and dest vertices are valid
      //
      if (src_vertex == (BiGraphVertex<TObject>*)NULL) {
	return Error::handle(name(), L"readArcDataText", Error::ARG,
			     __FILE__, __LINE__);
      }
      if (dst_vertex == (BiGraphVertex<TObject>*)NULL) {
	return Error::handle(name(), L"readArcDataText", Error::ARG,
			     __FILE__, __LINE__);
      }      
      
      // insert an arc between the source and dest vertices
      //
      const_cast<BiGraph<TObject>* >(this)->insertArc(src_vertex,
						      dst_vertex,
						      is_epsilon, weight);
    }
    else {

      // get the source and destination vertices
      //
      BiGraphVertex<TObject>* src_vertex = hash_table_a.get(source);
      BiGraphVertex<TObject>* dst_vertex = hash_table_a.get(dest);

      // make sure that the source and dest vertices are valid
      //
      if (src_vertex == (BiGraphVertex<TObject>*)NULL) {
	return Error::handle(name(), L"readArcDataText", Error::ARG,
			     __FILE__, __LINE__);
      }
      if (dst_vertex == (BiGraphVertex<TObject>*)NULL) {
	return Error::handle(name(), L"readArcDataText", Error::ARG,
			     __FILE__, __LINE__);
      }
      
      // insert an arc between the source and dest vertices
      //
      const_cast<BiGraph<TObject>* >(this)->insertArc(src_vertex,
						      dst_vertex, is_epsilon);
    }
  }
  
  // exit gracefully
  //
  return true;
}

// method: writeVertexDataText
//
// arguments:
//  Sof& sof: (input) sof file
//  DoubleLinkedList<TObject>& dlist: (input) list of graph vertex elements
//
// return: a boolean value indicating status
//
// write out the vertex data for this graph
//
template<class TObject>
boolean BiGraph<TObject>::writeVertexDataText(Sof& sof_a,
			    DoubleLinkedList<TObject>& data_a) const {

  // declare local variables
  //
  boolean more = false;
    
  // we need an empty string for the sub-parameter
  //
  String empty_str;
  String start_str;
  start_str.assign(SofParser::DEF_BLOCKSTART_CHAR);       // L"{"
  
  String end_str;
  end_str.assign(SofParser::DEF_BLOCKSTOP_CHAR);          // L"}"

  String delim0_str(end_str);
  delim0_str.concat(end_str);
  delim0_str.concat(SofParser::DEF_DELIMITER_CHAR);       
  delim0_str.concat(SofParser::NEWLINE_CHAR);
  delim0_str.concat(L'\t');  
  delim0_str.concat(start_str);                           // L"}},\n\t"

  String vertex_beg;
  vertex_beg.assign(SofParser::DEF_BLOCKSTART_CHAR);      // L"{"
  String vertex_end;
  vertex_end.assign(SofParser::DEF_BLOCKSTOP_CHAR);       // L"}"
  
  String delim1_str;
  delim1_str.concat(SofParser::DEF_DELIMITER_CHAR);
  delim1_str.concat(SofParser::SPACE_CHAR);		  
  delim1_str.concat(start_str);                           // L", {"

  String term(end_str);
  term.concat(end_str);
  term.concat(SofParser::DEF_TERMINATOR_CHAR);
  term.concat(SofParser::NEWLINE_CHAR);                   // L"}};\n"

  // write a parameter name
  //
  String output(PARAM_VERTICES);
  output.concat(SofParser::SPACE_CHAR);
  output.concat(SofParser::DEF_ASSIGNMENT_CHAR);
  output.concat(SofParser::NEWLINE_CHAR);
  output.concat(L'\t');
  sof_a.puts(output);

  // write the initial vertex start brace
  //
  output.assign(vertex_beg);
  sof_a.puts(output);
  
  // iterate over all vertices in the list and write them to file
  //
  for (more = const_cast<DoubleLinkedList<TObject>& >(data_a).gotoFirst();
       more;
       more = const_cast<DoubleLinkedList<TObject>& >(data_a).gotoNext()) {

    // write the position of the vertex in the list
    //
    Long position(data_a.getPosition());
    position.writeData(sof_a, String::getEmptyString());

    // write the delimiter
    //
    output.assign(delim1_str);
    sof_a.puts(output);
    
    // retrieve the current vertex data element
    //
    TObject* curr_obj =
      const_cast<DoubleLinkedList<TObject>& >(data_a).getCurr();

    // write the vertex data elements
    //
    curr_obj->writeData(sof_a, String::getEmptyString());

    // write the delimiter
    //
    TObject* last_obj =
      const_cast<DoubleLinkedList<TObject>& >(data_a).getLast();

    if (curr_obj == last_obj) {
      output.assign(term);
      sof_a.puts(output);
    }

    else {
      output.assign(delim0_str);
      sof_a.puts(output);
    }
  }

  // exit gracefully
  //
  return true;
}

// method: writeArcDataText
//
// arguments:
//  Sof& sof: (input) sof file
//  DoubleLinkedList<TopoTriple>& alist: (input) graph arcs and weights
//
// return: a boolean value indicating status
//
// write out the arc data for this graph
//
template<class TObject>
boolean BiGraph<TObject>::writeArcDataText(Sof& sof_a,
			   DoubleLinkedList<TopoTriple>& arcs_a) const {

  // we need an empty string for the sub-parameter
  //
  String empty_str;
  String start_str;
  start_str.assign(SofParser::DEF_BLOCKSTART_CHAR);       // L"{"
  
  String end_str;
  end_str.assign(SofParser::DEF_BLOCKSTOP_CHAR);          // L"}"

  String start_vertex_str;
  start_vertex_str.assign(START_NAME);                    // L"S"

  String term_vertex_str;
  term_vertex_str.assign(TERM_NAME);                      // L"T"  
  
  String delim0_str(end_str);
  delim0_str.concat(SofParser::DEF_DELIMITER_CHAR);       
  delim0_str.concat(SofParser::NEWLINE_CHAR);
  delim0_str.concat(L'\t');  
  delim0_str.concat(start_str);                           // L"},\n\t"

  String arc_beg;
  arc_beg.assign(SofParser::DEF_BLOCKSTART_CHAR);      // L"{"
  String arc_end;
  arc_end.assign(SofParser::DEF_BLOCKSTOP_CHAR);       // L"}"
  
  String delim1_str;
  delim1_str.concat(SofParser::DEF_DELIMITER_CHAR);
  delim1_str.concat(SofParser::SPACE_CHAR);		 // L", " 

  String term;
  term.assign(end_str);  
  term.concat(SofParser::DEF_TERMINATOR_CHAR);
  term.concat(SofParser::NEWLINE_CHAR);                   // L"};\n"

  // write a parameter name
  //
  String output(PARAM_ARCS);
  output.concat(SofParser::SPACE_CHAR);
  output.concat(SofParser::DEF_ASSIGNMENT_CHAR);
  output.concat(SofParser::NEWLINE_CHAR);
  output.concat(L'\t');
  sof_a.puts(output);

  // write the initial arc start brace
  //
  output.assign(arc_beg);
  sof_a.puts(output);

  // iterate over all vertices in the list and write them to file
  //
  for (boolean more =
	 const_cast<DoubleLinkedList<TopoTriple>& >(arcs_a).gotoFirst();
       more;
       more = const_cast<DoubleLinkedList<TopoTriple>& >(arcs_a).gotoNext()) {

    // retrieve and write the source vertex position of the current arc
    //
    Long src_pos = arcs_a.getCurr()->first().first();
    if (src_pos.eq(START_INDEX)) {
      output.assign(start_vertex_str);
      sof_a.puts(output);
    }

    else if (src_pos.eq(TERM_INDEX)) {
      output.assign(term_vertex_str);
      sof_a.puts(output);      
    }
    
    else {
      src_pos.writeData(sof_a, String::getEmptyString());
    }

    // write the parameter seperator
    //
    output.assign(delim1_str);
    sof_a.puts(output);
    
    // retrieve and write the destination vertex position of the current arc
    //
    Long dst_pos = arcs_a.getCurr()->first().second();
    if (dst_pos.eq(START_INDEX)) {
      output.assign(start_vertex_str);
      sof_a.puts(output);
    }

    else if (dst_pos.eq(TERM_INDEX)) {
      output.assign(term_vertex_str);
      sof_a.puts(output);      
    }
    
    else {
      dst_pos.writeData(sof_a, String::getEmptyString());
    }

    // retrieve and write the weight of the current arc
    //
    if (this->isWeighted()) {
      output.assign(delim1_str);
      sof_a.puts(output);      
      Float weight = arcs_a.getCurr()->second();
      weight.writeData(sof_a, String::getEmptyString());
    }
    
    // retrieve and write the epsilon flag of the current arc
    //
    Boolean epsilon = arcs_a.getCurr()->third();
    if (epsilon.eq(true)) {
      output.assign(delim1_str);
      sof_a.puts(output);      
      epsilon.writeData(sof_a, String::getEmptyString());
    }

    // write the arc terminator
    //
    if (arcs_a.getLast() == arcs_a.getCurr()) {
      output.assign(term);
      sof_a.puts(output);	
    }

    else {
      output.assign(delim0_str);
      sof_a.puts(output);
    }
  }
  
  // exit gracefully
  //
  return true;
}

// end of include file
//
#endif
