content: /*
   *    This program is free software; you can redistribute it and/or modify
   *    it under the terms of the GNU General Public License as published by
   *    the Free Software Foundation; either version 2 of the License, or
   *    (at your option) any later version.
   *
   *    This program is distributed in the hope that it will be useful,
   *    but WITHOUT ANY WARRANTY; without even the implied warranty of
   *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *    GNU General Public License for more details.
   *
   *    You should have received a copy of the GNU General Public License
   *    along with this program; if not, write to the Free Software
   *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   */
  
  /*
   * XMLBeans.java
   * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
   */
  
  package weka.gui.beans.xml;
  
  import weka.core.converters.ConverterUtils;
  import weka.core.xml.XMLBasicSerialization;
  import weka.core.xml.XMLDocument;
  import weka.core.Environment;
  import weka.core.EnvironmentHandler;
  import weka.gui.beans.BeanConnection;
  import weka.gui.beans.BeanInstance;
  import weka.gui.beans.BeanVisual;
  import weka.gui.beans.MetaBean;
  import weka.gui.beans.Visible;
  import weka.gui.beans.BeanCommon;
  
  import java.awt.Color;
  import java.awt.Dimension;
  import java.awt.Font;
  import java.awt.Point;
  import java.beans.BeanInfo;
  import java.beans.EventSetDescriptor;
  import java.beans.Introspector;
  import java.beans.beancontext.BeanContextSupport;
  import java.io.File;
  import java.util.Enumeration;
  import java.util.Hashtable;
  import java.util.StringTokenizer;
  import java.util.Vector;
  
  import javax.swing.JComponent;
  import javax.swing.JPanel;
  import javax.swing.plaf.ColorUIResource;
  import javax.swing.plaf.FontUIResource;
  
  import org.w3c.dom.Document;
  import org.w3c.dom.Element;
  import org.w3c.dom.NodeList;
  
  /**
   * This class serializes and deserializes a KnowledgeFlow setup to and fro XML.
   * <br>
   * 
   * @author FracPete (fracpete at waikato dot ac dot nz)
   * @version $Revision: 7808 $
   */
  public class XMLBeans 
    extends XMLBasicSerialization {
  
    /** the value of the id property */
    public final static String VAL_ID = "id";
  
    /** the value of the x property */
    public final static String VAL_X = "x";
  
    /** the value of the y property */
    public final static String VAL_Y = "y";
  
    /** the value of the bean property */
    public final static String VAL_BEAN = "bean";
  
    /** the value of the customName property */
    public final static String VAL_CUSTOM_NAME = "custom_name";
   
    /** the value of the source property */
    public final static String VAL_SOURCEID = "source_id";
   
    /** the value of the target property */
    public final static String VAL_TARGETID = "target_id";
    
    /** the value of the eventname property */
    public final static String VAL_EVENTNAME = "eventname";
    
    /** the value of the hidden property */
    public final static String VAL_HIDDEN = "hidden";
    
    /** the value of the file property */
    public final static String VAL_FILE = "file";
    
    /** the value of the dir property */
    public final static String VAL_DIR = "dir";
    
    /** the value of the prefix property */
    public final static String VAL_PREFIX = "prefix";
  
    public final static String VAL_RELATIVE_PATH = "useRelativePath";
    
    /** the value of the options property */
    public final static String VAL_OPTIONS = "options";
    
    /** the value of the saver property */
    public final static String VAL_SAVER = "wrappedAlgorithm";
    
    /** the value of the loader property */
    public final static String VAL_LOADER = "wrappedAlgorithm";
    
    /** the value of the text property */
    public final static String VAL_TEXT = "text";
    
    /** the value of the beanContext property */
    public final static String VAL_BEANCONTEXT = "beanContext";
  
    /** the value of the width property */
    public final static String VAL_WIDTH = "width";
  
    /** the value of the height property */
    public final static String VAL_HEIGHT = "height";
  
    /** the value of the red property */
    public final static String VAL_RED = "red";
  
    /** the value of the green property */
    public final static String VAL_GREEN = "green";
    
    /** the value of the blue property */
    public final static String VAL_BLUE = "blue";
  
    /** the value of the value property */
    public final static String VAL_NAME = "name";
  
    /** the value of the style property */
    public final static String VAL_STYLE = "style";
   
    /** the value of the location property */
    public final static String VAL_LOCATION = "location";
   
    /** the value of the size property */
    public final static String VAL_SIZE = "size";
   
    /** the value of the color property */
    public final static String VAL_COLOR = "color";
    
    /** the value of the font property */
    public final static String VAL_FONT = "font";
    
    /** the value of the iconpath property */
    public final static String VAL_ICONPATH = "iconPath";
    
    /** the value of the animatedIconPath property */
    public final static String VAL_ANIMATEDICONPATH = "animatedIconPath";
    
    /** the value of the associatedConnections property */
    public final static String VAL_ASSOCIATEDCONNECTIONS = "associatedConnections";
    
    /** the value of the input property */
    public final static String VAL_INPUTS = "inputs";
    
    /** the value of the input id property */
    public final static String VAL_INPUTSID = "inputs_id";
    
    /** the value of the outputs id property */
    public final static String VAL_OUTPUTS = "outputs";
    
    /** the value of the outputs property */
    public final static String VAL_OUTPUTSID = "outputs_id";
    
    /** the value of the subFlow property */
    public final static String VAL_SUBFLOW = "subFlow";
    
    /** the value of the originalCoords property */
    public final static String VAL_ORIGINALCOORDS = "originalCoords";
    
    /** the value of the relationNameForFilename property (Saver) */
    public final static String VAL_RELATIONNAMEFORFILENAME = "relationNameForFilename";
  
    /** the index in the Vector, where the BeanInstances are stored 
     * (Instances and Connections are stored in a Vector and then serialized) */
    public final static int INDEX_BEANINSTANCES = 0;
  
    /** the index in the Vector, where the BeanConnections are stored
    * (Instances and Connections are stored in a Vector and then serialized) */
    public final static int INDEX_BEANCONNECTIONS = 1;
    
    /** the component that manages the layout of the beans */
    protected JComponent m_BeanLayout;
    
    /** keeps track of the BeanInstances read so far, used for the BeanConnections */
    protected Vector m_BeanInstances;
    
    /** keeps track of the BeanInstances read so far, used for the BeanConnections */
    protected Vector m_BeanInstancesID;
  
    /** whether to ignore the BeanConnection */
    protected boolean m_IgnoreBeanConnections;
    
    /** the current MetaBean (for the BeanConnections) */
    protected MetaBean m_CurrentMetaBean;
  
    /** the identifier for regular BeanConnections */
    protected final static String REGULAR_CONNECTION = "regular_connection";
    
    /** the relation between Bean and connection, MetaBean BeanConnections
     * are stored under the reference of the MetaBean, regular connections 
     * are stored under REGULAR_CONNECTION. The relation has the following 
     * format (is a string): sourcePos,targetPos,event,hidden
     * @see #REGULAR_CONNECTION */
    protected Hashtable m_BeanConnectionRelation;
    
    /** the data that is about to be read/written contains a complete layout 
     * @see #m_DataType */
    public final static int DATATYPE_LAYOUT = 0;
    
    /** the data that is about to be read/written contains user-components, i.e., 
     * Metabeans 
     * @see #m_DataType */
    public final static int DATATYPE_USERCOMPONENTS = 1;
    
    /** the type of data that is be read/written
     * @see #DATATYPE_LAYOUT
     * @see #DATATYPE_USERCOMPONENTS */
    protected int m_DataType = DATATYPE_LAYOUT;
    
    /** the beancontext to use for loading from XML and the beancontext is
     * null in the bean */
    protected BeanContextSupport m_BeanContextSupport = null;
    
    /** The index of the vector of bean instances or connections to use.
     * this corresponds to a tab in the main KnowledgeFlow UI
     */
    protected int m_vectorIndex = 0;
    
    /**
     * initializes the serialization for layouts
     * 
     * @param layout      the component that manages the layout
     * @param context     the bean context support to use
     * @param tab         the index of the vector of bean instances or connections
     * to use (this corresponds to a visible tab in the main KnowledgeFlow UI)
     * @throws Exception  if initialization fails
     */
    public XMLBeans(JComponent layout, BeanContextSupport context, int tab) throws Exception {
      this(layout, context, DATATYPE_LAYOUT, tab);
    }
    
    /**
     * initializes the serialization for different types of data
     * 
     * @param layout      the component that manages the layout
     * @param context     the bean context support to use
     * @param datatype    the type of data to read/write
     * @throws Exception  if initialization fails
     */
    public XMLBeans(JComponent layout, BeanContextSupport context, int datatype, 
        int tab) throws Exception {
      super();
      
      m_vectorIndex = tab;
      m_BeanLayout = layout;
      m_BeanContextSupport = context;
      setDataType(datatype);
    }
    
    /**
     * sets what kind of data is to be read/written
     * @param value       the type of data
     * @see #m_DataType
     */
    public void setDataType(int value) {
      if (value == DATATYPE_LAYOUT)
        m_DataType = value;
      else if (value == DATATYPE_USERCOMPONENTS)
        m_DataType = value;
      else
        System.out.println("DataType '" + value + "' is unknown!");
    }
    
    /**
     * returns the type of data that is to be read/written
     * @return the type of data
     * @see #m_DataType
     */
    public int getDataType() {
      return m_DataType;
    }
  
    /**
     * generates internally a new XML document and clears also the IgnoreList and
     * the mappings for the Read/Write-Methods
     * 
     * @throws Exception if something goes wrong
     */
    public void clear() throws Exception {
      Vector<String>	classnames;
      int			i;
      
      super.clear();
      
      // ignore: suppress unnecessary GUI stuff 
      // needs to be checked for new Java versions (might introduce new properties) 
      // - works with Java 1.5
      m_Properties.addIgnored("UI");
      m_Properties.addIgnored("actionMap");
      m_Properties.addIgnored("alignmentX");
      m_Properties.addIgnored("alignmentY");
      m_Properties.addIgnored("autoscrolls");
      m_Properties.addIgnored("background");
      m_Properties.addIgnored("border");
      m_Properties.addIgnored("componentPopupMenu");
      m_Properties.addIgnored("debugGraphicsOptions");
      m_Properties.addIgnored("doubleBuffered");
      m_Properties.addIgnored("enabled");
      m_Properties.addIgnored("focusCycleRoot");
      m_Properties.addIgnored("focusTraversalPolicy");
      m_Properties.addIgnored("focusTraversalPolicyProvider");
      m_Properties.addIgnored("focusable");
      m_Properties.addIgnored("font");
      m_Properties.addIgnored("foreground");
      m_Properties.addIgnored("inheritsPopupMenu");
      m_Properties.addIgnored("inputVerifier");
      m_Properties.addIgnored("layout");
      m_Properties.addIgnored("locale");
      m_Properties.addIgnored("maximumSize");
      m_Properties.addIgnored("minimumSize");
      m_Properties.addIgnored("nextFocusableComponent");
      m_Properties.addIgnored("opaque");
      m_Properties.addIgnored("preferredSize");
      m_Properties.addIgnored("requestFocusEnabled");
      m_Properties.addIgnored("toolTipText");
      m_Properties.addIgnored("transferHandler");
      m_Properties.addIgnored("verifyInputWhenFocusTarget");
      m_Properties.addIgnored("visible");
  
      // special ignore
      m_Properties.addIgnored("size");  // otherwise you get an endless loop with Dimension!
      m_Properties.addIgnored("location");  // otherwise you get an endless loop with Point!
  
      // allow
      m_Properties.addAllowed(weka.gui.beans.BeanInstance.class, "x");
      m_Properties.addAllowed(weka.gui.beans.BeanInstance.class, "y");
      m_Properties.addAllowed(weka.gui.beans.BeanInstance.class, "bean");
      m_Properties.addAllowed(weka.gui.beans.Saver.class, "wrappedAlgorithm");
      m_Properties.addAllowed(weka.gui.beans.Loader.class, "wrappedAlgorithm");
      m_Properties.addAllowed(weka.gui.beans.Saver.class, "relationNameForFilename");
      if (getDataType() == DATATYPE_LAYOUT)
        m_Properties.addAllowed(weka.gui.beans.Loader.class, "beanContext");
      else
        m_Properties.addIgnored(weka.gui.beans.Loader.class, "beanContext");   // TODO: more classes???
      m_Properties.addAllowed(weka.gui.beans.Filter.class, "filter");
      m_Properties.addAllowed(weka.gui.beans.Associator.class, "associator");
      m_Properties.addAllowed(weka.gui.beans.Classifier.class, "wrappedAlgorithm");
      m_Properties.addAllowed(weka.gui.beans.Clusterer.class, "wrappedAlgorithm");
      m_Properties.addAllowed(weka.gui.beans.Classifier.class, "executionSlots");
      m_Properties.addAllowed(weka.gui.beans.Classifier.class, "blockOnLastFold");
  
      m_Properties.addAllowed(weka.classifiers.Classifier.class, "debug");
      m_Properties.addAllowed(weka.classifiers.Classifier.class, "options");
      m_Properties.addAllowed(weka.associations.Associator.class, "options");
      m_Properties.addAllowed(weka.clusterers.Clusterer.class, "options");
      m_Properties.addAllowed(weka.filters.Filter.class, "options");
      
      m_Properties.addAllowed(weka.core.converters.DatabaseSaver.class, "options");
      m_Properties.addAllowed(weka.core.converters.DatabaseLoader.class, "options");
      m_Properties.addAllowed(weka.core.converters.TextDirectoryLoader.class, "options");
  
      // we assume that classes implementing SplitEvaluator also implement OptionHandler
      m_Properties.addAllowed(weka.experiment.SplitEvaluator.class, "options");
      // we assume that classes implementing ResultProducer also implement OptionHandler
      m_Properties.addAllowed(weka.experiment.ResultProducer.class, "options");
  
      // read/write methods
      m_CustomMethods.register(this, Color.class, "Color");
      m_CustomMethods.register(this, Dimension.class, "Dimension");
      m_CustomMethods.register(this, Font.class, "Font");
      m_CustomMethods.register(this, Point.class, "Point");
      m_CustomMethods.register(this, ColorUIResource.class, "ColorUIResource");
      m_CustomMethods.register(this, FontUIResource.class, "FontUIResource");
  
      m_CustomMethods.register(this, weka.gui.beans.BeanInstance.class, "BeanInstance");
      m_CustomMethods.register(this, weka.gui.beans.BeanConnection.class, "BeanConnection");
      m_CustomMethods.register(this, weka.gui.beans.BeanVisual.class, "BeanVisual");
      m_CustomMethods.register(this, weka.gui.beans.Saver.class, "BeanSaver");
      m_CustomMethods.register(this, weka.gui.beans.MetaBean.class, "MetaBean");
  
      classnames = ConverterUtils.getFileLoaders();
      for (i = 0; i < classnames.size(); i++)
        m_CustomMethods.register(this, Class.forName(classnames.get(i)), "Loader");
      classnames = ConverterUtils.getFileSavers();
      for (i = 0; i < classnames.size(); i++)
        m_CustomMethods.register(this, Class.forName(classnames.get(i)), "Saver");
      
      // other variables
      m_BeanInstances          = null;
      m_BeanInstancesID        = null;
      m_CurrentMetaBean        = null;
      m_IgnoreBeanConnections  = true;
      m_BeanConnectionRelation = null;
    }
    
    /**
     * traverses over all BeanInstances (or MetaBeans) and stores them in a vector 
     * (recurses into MetaBeans, since the sub-BeanInstances are not visible)
     * @param list       the BeanInstances/MetaBeans to traverse
     */
    protected void addBeanInstances(Vector list) {
      int             i;
      BeanInstance    beaninst;
      
      for (i = 0; i < list.size(); i++) {
        if (list.get(i) instanceof BeanInstance) {
          beaninst = (BeanInstance) list.get(i);
          
          m_BeanInstancesID.add(new Integer(m_BeanInstances.size()));
          m_BeanInstances.add(beaninst);
          
          if (beaninst.getBean() instanceof MetaBean)
            addBeanInstances(((MetaBean) beaninst.getBean()).getBeansInSubFlow());
        }
        else if (list.get(i) instanceof MetaBean) {
          addBeanInstances(((MetaBean) list.get(i)).getBeansInSubFlow());
        }
        else {
          System.out.println("addBeanInstances does not support Vectors of class '" + list.get(i) + "'!");
        }
      }
    }
    
    /**
     * enables derived classes to due some pre-processing on the objects, that's
     * about to be serialized. Right now it only returns the object.
     * 
     * @param o the object that is serialized into XML
     * @return the possibly altered object
     * @throws Exception if post-processing fails
     */
    protected Object writePreProcess(Object o) throws Exception {
      o = super.writePreProcess(o);
      
      // gather all BeanInstances, also the ones in MetaBeans
      m_BeanInstances   = new Vector();
      m_BeanInstancesID = new Vector();
      
      switch (getDataType()) {
        case DATATYPE_LAYOUT:
          addBeanInstances(BeanInstance.getBeanInstances(m_vectorIndex));
          break;
  
        case DATATYPE_USERCOMPONENTS:
          addBeanInstances((Vector) o);
          break;
          
        default:
          System.out.println("writePreProcess: data type '" + getDataType() + "' is not recognized!");
          break;
      }
      
      return o;
    }
    
    /**
     * enables derived classes to add other properties to the DOM tree, e.g.
     * ones that do not apply to the get/set convention of beans. only implemented
     * with empty method body.
     * 
     * @param o the object that is serialized into XML
     * @throws Exception if post-processing fails
     */
    protected void writePostProcess(Object o) throws Exception {
      Element         root;
      NodeList        list;
      Element         conns;
      Element         child;
      int             i;
  
      // since not all BeanConnections get saved to XML (e.g., MetaBeans in the 
      // UserToolBar) if one saves a layout, the numbering in the Vector of the 
      // BeanConnections is not correct. The "name" attribute of the nodes has
      // to be modified
      if (getDataType() == DATATYPE_LAYOUT) {
        root  = m_Document.getDocument().getDocumentElement();
        conns = (Element) root.getChildNodes().item(INDEX_BEANCONNECTIONS);
        list  = conns.getChildNodes();
        for (i = 0; i < list.getLength(); i++) {
          child = (Element) list.item(i);
          child.setAttribute(ATT_NAME, "" + i);
        }
      }
    }
    
    /**
     * additional pre-processing can happen in derived classes before the 
     * actual reading from XML (working on the raw XML). right now it does 
     * nothing with the document, only empties the help-vector for the
     * BeanInstances and reads the IDs for the BeanInstances, s.t. the correct
     * references can be set again
     * 
     * @param document the document to pre-process
     * @return the processed object
     * @throws Exception if post-processing fails
     * @see #m_BeanInstances
     */
    protected Document readPreProcess(Document document) throws Exception {
      NodeList        list;
      int             i;
      Element         node;
      String          clsName;
      Vector          children;
      int             id;
      int             n;
      Element         child;
      
      m_BeanInstances   = new Vector();
      m_BeanInstancesID = new Vector();
      
      // get all BeanInstance nodes
      list    = document.getElementsByTagName("*");
      clsName = BeanInstance.class.getName();
      for (i = 0; i < list.getLength(); i++) {
        node = (Element) list.item(i);
        
        // is it a BeanInstance?
        if (node.getAttribute(ATT_CLASS).equals(clsName)) {
          children = XMLDocument.getChildTags(node);
          id       = m_BeanInstancesID.size();
          
          // get id-tag (if available)
          for (n = 0; n < children.size(); n++) {
            child = (Element) children.get(n);
            if (child.getAttribute(ATT_NAME).equals(VAL_ID))
              id = readIntFromXML((Element) child);
          }
          
          m_BeanInstancesID.add(new Integer(id));
        }
      }
      
      m_BeanInstances.setSize(m_BeanInstancesID.size());
      
      // set MetaBean to null
      m_CurrentMetaBean = null;
  
      // no BeanConnections -> see readPostProcess(Object)
      m_IgnoreBeanConnections = true;
      
      // reset BeanConnection-Relations
      m_BeanConnectionRelation = new Hashtable();
      
      return document;
    }
    
    /**
     * puts the given BeanConnection onto the next null in the given Vector,
     * or at the end of the list, if no null is found.
     * (during the de-serializing, no BeanConnections are set, only nulls)
     * @param conn      the connection to add to the list
     * @param list      the list to add the BeanConnection to
     */
    protected void setBeanConnection(BeanConnection conn, Vector list) {
      int         i;
      boolean     added;
      
      added = false;
      for (i = 0; i < list.size(); i++) {
        if (list.get(i) == null) {
          list.set(i, conn);
          added = true;
          break;
        }
      }
      
      if (!added)
        list.add(conn);
    }
    
    /**
     * generates a connection based on the given parameters
     * @param sourcePos the source position in the m_BeanInstances vector
     * @param targetPos the target position in the m_BeanInstances vector
     * @param event the name of the event, i.e., the connection
     * @param hidden true if the connection is hidden
     * @return the generated BeanConnection
     * @throws Exception if something goes wrong
     */
    protected BeanConnection createBeanConnection(int sourcePos, int targetPos, String event, boolean hidden) throws Exception {
      BeanConnection          result;
      BeanInfo                compInfo;
      EventSetDescriptor[]    esds;
      int                     i;
      BeanInstance            instSource;
      BeanInstance            instTarget;
  
      result = null;
      
      // was there a connection?
      if ( (sourcePos == -1) || (targetPos == -1) )
        return result;
      
      instSource = (BeanInstance) m_BeanInstances.get(sourcePos);
      instTarget = (BeanInstance) m_BeanInstances.get(targetPos);
      
      compInfo = Introspector.getBeanInfo(((BeanInstance) m_BeanInstances.get(sourcePos)).getBean().getClass());
      esds     = compInfo.getEventSetDescriptors();
  
      for (i = 0; i < esds.length; i++) {
        if (esds[i].getName().equals(event)) {
          result = new BeanConnection(instSource, instTarget, esds[i], m_vectorIndex);
          ((BeanConnection) result).setHidden(hidden);
          break;
        }
      }
      
      return result;
    }
    
    /**
     * rebuilds all the connections for a certain key in the hashtable.
     * for the ones being part of a MetaBean, no new instance is built, but only
     * the reference to the actual BeanConnection set.
     * @param deserialized    the deserialized knowledgeflow
     * @param key             the key of the hashtable to rebuild all connections for
     * @throws Exception if something goes wrong
     */
    protected void rebuildBeanConnections(Vector deserialized, Object key) throws Exception {
      int                     i;
      int                     n;
      int                     sourcePos;
      int                     targetPos;
      String                  event;
      boolean                 hidden;
      Vector                  conns;
      BeanConnection          conn;
      StringTokenizer         tok;
      Vector                  beanconns;
  
      conns = (Vector) m_BeanConnectionRelation.get(key);
      
      // no connections?
      if (conns == null)
        return;
      
      for (n = 0; n < conns.size(); n++) {
        tok       = new StringTokenizer(conns.get(n).toString(), ",");
        conn      = null;
        sourcePos = Integer.parseInt(tok.nextToken());
        targetPos = Integer.parseInt(tok.nextToken());
        event     = tok.nextToken();
        hidden    = stringToBoolean(tok.nextToken());
  
        // regular connection? -> new instance
        // or MetaBean from user toolbar
        if ( (!(key instanceof MetaBean)) || (getDataType() == DATATYPE_USERCOMPONENTS)) {
          conn = createBeanConnection(sourcePos, targetPos, event, hidden);
        }
        // MetaBean? -> find BeanConnection 
        else {
          beanconns = BeanConnection.getConnections(m_vectorIndex);
          
          for (i = 0; i < beanconns.size(); i++) {
            conn = (BeanConnection) beanconns.get(i);
            if (    (conn.getSource() == (BeanInstance) m_BeanInstances.get(sourcePos))
                 && (conn.getTarget() == (BeanInstance) m_BeanInstances.get(targetPos))
                 && (conn.getEventName().equals(event)) ) {
              break;
            }
            conn = null;
          }
        }
        
        // add the connection to the corresponding list/MetaBean
        if (key instanceof MetaBean)
          setBeanConnection(conn, ((MetaBean) key).getAssociatedConnections());
        else
          setBeanConnection(conn, (Vector) deserialized.get(INDEX_BEANCONNECTIONS));
      }
    }
    
    /**
     * removes the given meta beans from the layout, since they're only listed
     * in the user toolbar
     * 
     * @param metabeans         the list of MetaBeans in the user toolbar
     */
    protected void removeUserToolBarBeans(Vector metabeans) {
      int           i;
      int           n;
      MetaBean      meta;
      Vector        subflow;
      BeanInstance  beaninst;
      
      for (i = 0; i < metabeans.size(); i++) {
        meta    = (MetaBean) metabeans.get(i);
        subflow = meta.getSubFlow();
        
        for (n = 0; n < subflow.size(); n++) {
          beaninst = (BeanInstance) subflow.get(n);
          beaninst.removeBean(m_BeanLayout);
        }
      }
    }
    
    /**
     * additional post-processing can happen in derived classes after reading 
     * from XML. re-builds the BeanConnections.
     * 
     * @param o the object to perform some additional processing on
     * @return the processed object
     * @throws Exception if post-processing fails
     */
    protected Object readPostProcess(Object o) throws Exception {
      Enumeration             enm;
      Vector                  deserialized;
      Object                  key;
  
      deserialized = (Vector) super.readPostProcess(o);
      
      // rebuild the actual connections
      rebuildBeanConnections(deserialized, REGULAR_CONNECTION);
  
      // rebuild the references in the MetaBeans
      enm = m_BeanConnectionRelation.keys();
      while (enm.hasMoreElements()) {
        key = enm.nextElement();
        
        // skip the regular connections
        if (!(key instanceof MetaBean))
          continue;
        
        rebuildBeanConnections(deserialized, key);
      }
  
      // remove MetaBean and subflow from BeanInstance (not part of the flow!)
      if (getDataType() == DATATYPE_USERCOMPONENTS)
        removeUserToolBarBeans(deserialized);
      
      return deserialized;
    }
  
    /**
     * returns the relation for the given MetaBean, for the regular connections,
     * null has to be used
     * @param meta      the MetaBean (or null for regular connections) to retrieve
     *                  the connections for
     * @return          the associated connections
     * @see #REGULAR_CONNECTION
     */
    protected Vector getBeanConnectionRelation(MetaBean meta) {
      Vector      result;
      Object      key;
      
      if (meta == null)
        key = REGULAR_CONNECTION;
      else
        key = meta;
      
      // not yet in there?
      if (!m_BeanConnectionRelation.containsKey(key)) {
        m_BeanConnectionRelation.put(key, new Vector());
      }
  
      result = (Vector) m_BeanConnectionRelation.get(key);
      
      return result;
    }
    
    /**
     * adds the given connection-relation for the specified MetaBean (or null in 
     * case of regular connections)
     * @param meta        the MetaBean (or null for regular connections) to add
     *                    the relationship for
     * @param connection  the connection relation to add
     */
    protected void addBeanConnectionRelation(MetaBean meta, String connection) {
      Vector      relations;
      Object      key;
      
      relations = getBeanConnectionRelation(meta);
      
      // add relation
      relations.add(connection);
      
      // update
      if (meta == null)
        key = REGULAR_CONNECTION;
      else
        key = meta;
      m_BeanConnectionRelation.put(key, relations);
    }
    
    /**
     * adds the given Color to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writeColor(Element parent, Object o, String name)
      throws Exception {
      
      Element     node;
      Color       color;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      color = (Color) o;
      node  = addElement(parent, name, color.getClass().getName(), false);
  
      writeIntToXML(node, color.getRed(), VAL_RED);
      writeIntToXML(node, color.getGreen(), VAL_GREEN);
      writeIntToXML(node, color.getBlue(), VAL_BLUE);
      
      return node;
    }
  
    /**
     * builds the Color from the given DOM node.
     * 
     * @param node the associated XML node
     * @return the instance created from the XML description
     * @throws Exception if instantiation fails
     */
    public Object readColor(Element node) throws Exception {
      Object      result;
      Vector      children;
      Element     child;
      int         i;
      int         red;
      int         green;
      int         blue;
      String      name;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), node.getAttribute(ATT_NAME));
  
      m_CurrentNode = node;
      
      result   = null;
      children = XMLDocument.getChildTags(node);
      red      = 0;
      green    = 0;
      blue     = 0;
  
      for (i = 0; i < children.size(); i++) {
        child = (Element) children.get(i);
        name  = child.getAttribute(ATT_NAME);
  
        if (name.equals(VAL_RED))
          red = readIntFromXML(child);
        else if (name.equals(VAL_GREEN))
          green = readIntFromXML(child);
        else if (name.equals(VAL_BLUE))
          blue = readIntFromXML(child);
        else
          System.out.println("WARNING: '" + name
              + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
      }
      
      result = new Color(red, green, blue);
  
      return result;
    }
  
    /**
     * adds the given Dimension to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writeDimension(Element parent, Object o, String name)
        throws Exception {
      
      Element     node;
      Dimension   dim;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      dim = (Dimension) o;
      node = addElement(parent, name, dim.getClass().getName(), false);
  
      writeDoubleToXML(node, dim.getWidth(), VAL_WIDTH);
      writeDoubleToXML(node, dim.getHeight(), VAL_HEIGHT);
      
      return node;
    }
  
    /**
     * builds the Dimension from the given DOM node.
     * 
     * @param node the associated XML node
     * @return the instance created from the XML description
     * @throws Exception if instantiation fails
     */
    public Object readDimension(Element node) throws Exception {
      Object      result;
      Vector      children;
      Element     child;
      int         i;
      double      width;
      double      height;
      String      name;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), node.getAttribute(ATT_NAME));
  
      m_CurrentNode = node;
      
      result   = null;
      children = XMLDocument.getChildTags(node);
      width    = 0;
      height   = 0;
  
      for (i = 0; i < children.size(); i++) {
        child = (Element) children.get(i);
        name  = child.getAttribute(ATT_NAME);
  
        if (name.equals(VAL_WIDTH))
          width = readDoubleFromXML(child);
        else if (name.equals(VAL_HEIGHT))
          height = readDoubleFromXML(child);
        else
          System.out.println("WARNING: '" + name
              + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
      }
      
      result = new Dimension();
      ((Dimension) result).setSize(width, height);
  
      return result;
    }
  
    /**
     * adds the given Font to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writeFont(Element parent, Object o, String name)
        throws Exception {
      
      Element     node;
      Font        font;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      font = (Font) o;
      node = addElement(parent, name, font.getClass().getName(), false);
  
      invokeWriteToXML(node, font.getName(), VAL_NAME);
      writeIntToXML(node, font.getStyle(), VAL_STYLE);
      writeIntToXML(node, font.getSize(), VAL_SIZE);
      
      return node;
    }
  
    /**
     * builds the Font from the given DOM node.
     * 
     * @param node the associated XML node
     * @return the instance created from the XML description
     * @throws Exception if instantiation fails
     */
    public Object readFont(Element node) throws Exception {
      Object      result;
      Vector      children;
      Element     child;
      int         i;
      int         style;
      int         size;
      String      name;
      String      fontname;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), node.getAttribute(ATT_NAME));
  
      m_CurrentNode = node;
      
      result   = null;
      children = XMLDocument.getChildTags(node);
      fontname = "";
      style    = 0;
      size     = 0;
  
      for (i = 0; i < children.size(); i++) {
        child = (Element) children.get(i);
        name  = child.getAttribute(ATT_NAME);
  
        if (name.equals(VAL_NAME))
          name = (String) invokeReadFromXML(child);
        else if (name.equals(VAL_STYLE))
          style = readIntFromXML(child);
        else if (name.equals(VAL_SIZE))
          size = readIntFromXML(child);
        else
          System.out.println("WARNING: '" + name
              + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
      }
      
      result = new Font(fontname, style, size);
  
      return result;
    }
  
    /**
     * adds the given Point to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writePoint(Element parent, Object o, String name)
        throws Exception {
      
      Element     node;
      Point       p;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      p    = (Point) o;
      node = addElement(parent, name, p.getClass().getName(), false);
  
      writeDoubleToXML(node, p.getX(), VAL_X);
      writeDoubleToXML(node, p.getY(), VAL_Y);
      
      return node;
    }
  
    /**
     * builds the Point from the given DOM node.
     * 
     * @param node the associated XML node
     * @return the instance created from the XML description
     * @throws Exception if instantiation fails
     */
    public Object readPoint(Element node) throws Exception {
      Object      result;
      Vector      children;
      Element     child;
      int         i;
      double      x;
      double      y;
      String      name;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), node.getAttribute(ATT_NAME));
  
      m_CurrentNode = node;
      
      result   = null;
      children = XMLDocument.getChildTags(node);
      x        = 0;
      y        = 0;
  
      for (i = 0; i < children.size(); i++) {
        child = (Element) children.get(i);
        name  = child.getAttribute(ATT_NAME);
  
        if (name.equals(VAL_X))
          x = readDoubleFromXML(child);
        else if (name.equals(VAL_Y))
          y = readDoubleFromXML(child);
        else
          System.out.println("WARNING: '" + name
              + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
      }
      
      result = new Point();
      ((Point) result).setLocation(x, y);
  
      return result;
    }
  
    /**
     * adds the given ColorUIResource to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writeColorUIResource(Element parent, Object o, String name)
        throws Exception {
      
      Element           node;
      ColorUIResource   resource;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      resource = (ColorUIResource) o;
      node     = addElement(parent, name, resource.getClass().getName(), false);
      invokeWriteToXML(node, new Color(resource.getRGB()), VAL_COLOR);
      
      return node;
    }
  
    /**
     * builds the ColorUIResource from the given DOM node.
     * 
     * @param node the associated XML node
     * @return the instance created from the XML description
     * @throws Exception if instantiation fails
     */
    public Object readColorUIResource(Element node) throws Exception {
      Object      result;
      Vector      children;
      Element     child;
      int         i;
      String      name;
      Color       color;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), node.getAttribute(ATT_NAME));
  
      m_CurrentNode = node;
      
      result   = null;
      children = XMLDocument.getChildTags(node);
      color    = null;
  
      for (i = 0; i < children.size(); i++) {
        child = (Element) children.get(i);
        name  = child.getAttribute(ATT_NAME);
  
        if (name.equals(VAL_COLOR))
          color = (Color) invokeReadFromXML(child);
        else
          System.out.println("WARNING: '" + name
              + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
      }
      
      result = new ColorUIResource(color);
  
      return result;
    }
  
    /**
     * adds the given FontUIResource to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writeFontUIResource(Element parent, Object o, String name)
        throws Exception {
      
      Element           node;
      FontUIResource    resource;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      resource = (FontUIResource) o;
      node     = addElement(parent, name, resource.getClass().getName(), false);
      invokeWriteToXML(node, new Font(resource.getName(), resource.getStyle(), resource.getSize()), VAL_COLOR);
      
      return node;
    }
  
    /**
     * builds the FontUIResource from the given DOM node.
     * 
     * @param node the associated XML node
     * @return the instance created from the XML description
     * @throws Exception if instantiation fails
     */
    public Object readFontUIResource(Element node) throws Exception {
      Object      result;
      Vector      children;
      Element     child;
      int         i;
      String      name;
      Font        font;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), node.getAttribute(ATT_NAME));
  
      m_CurrentNode = node;
      
      result   = null;
      children = XMLDocument.getChildTags(node);
      font     = null;
  
      for (i = 0; i < children.size(); i++) {
        child = (Element) children.get(i);
        name  = child.getAttribute(ATT_NAME);
  
        if (name.equals(VAL_FONT))
          font = (Font) invokeReadFromXML(child);
        else
          System.out.println("WARNING: '" + name
              + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
      }
      
      result = new FontUIResource(font);
  
      return result;
    }
  
    /**
     * adds the given BeanInstance to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writeBeanInstance(Element parent, Object o, String name)
        throws Exception {
      
      Element         node;
      BeanInstance    beaninst;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      beaninst = (BeanInstance) o;
      node     = addElement(parent, name, beaninst.getClass().getName(), false);
  
      writeIntToXML(node, m_BeanInstances.indexOf(beaninst), VAL_ID);
      writeIntToXML(node, beaninst.getX() + beaninst.getWidth()  / 2, VAL_X);   // x is thought to be in the center?
      writeIntToXML(node, beaninst.getY() + beaninst.getHeight() / 2, VAL_Y);   // y is thought to be in the center?
      if (beaninst.getBean() instanceof BeanCommon) {
        // write the custom name of this bean
        String custName = ((BeanCommon)beaninst.getBean()).getCustomName();
        invokeWriteToXML(node, custName, VAL_CUSTOM_NAME);
      }
      invokeWriteToXML(node, beaninst.getBean(), VAL_BEAN);
      
      return node;
    }
  
    /**
     * builds the BeanInstance from the given DOM node.
     * 
     * @param node the associated XML node
     * @return the instance created from the XML description
     * @throws Exception if instantiation fails
     */
    public Object readBeanInstance(Element node) throws Exception {
      Object          result;
      Vector          children;
      Element         child;
      String          name;
      int             i;
      int             x;
      int             y;
      int             id;
      Object          bean;
      BeanVisual      visual;
      BeanInstance    beaninst;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), node.getAttribute(ATT_NAME));
  
      m_CurrentNode = node;
      
      result   = null;
      children = XMLDocument.getChildTags(node);
      id       = -1;
      x        = 0;
      y        = 0;
      bean     = null;
      String customName = null;
  
      for (i = 0; i < children.size(); i++) {
        child = (Element) children.get(i);
        name  = child.getAttribute(ATT_NAME);
  
        if (name.equals(VAL_ID)) {
          id = readIntFromXML(child);
        } else if (name.equals(VAL_X)) {
          x = readIntFromXML(child);
        } else if (name.equals(VAL_Y)) {
          y = readIntFromXML(child);
        } else if (name.equals(VAL_CUSTOM_NAME)) {
          customName = (String)invokeReadFromXML(child);
        } else if (name.equals(VAL_BEAN)) {
          bean = invokeReadFromXML(child);
        } else {
          System.out.println("WARNING: '" + name
              + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
        }
      }
      
      result   = new BeanInstance(m_BeanLayout, bean, x, y, m_vectorIndex);  
      beaninst = (BeanInstance) result;
      
      // set parent of BeanVisual
      if (beaninst.getBean() instanceof weka.gui.beans.Visible) {
        visual = ((Visible) beaninst.getBean()).getVisual();
        visual.setSize(visual.getPreferredSize());
        if (visual.getParent() == null) {
          ((JPanel) beaninst.getBean()).add(visual);
        }
      }
  
      if (beaninst.getBean() instanceof BeanCommon &&
          customName != null) {
        ((BeanCommon)beaninst.getBean()).setCustomName(customName);
      }
      
      // no IDs -> get next null position
      if (id == -1) {
        for (i = 0; i < m_BeanInstances.size(); i++) {
          if (m_BeanInstances.get(i) == null) {
            id = ((Integer) m_BeanInstancesID.get(i)).intValue();
            break;
          }
        }
      }
      // get position for id
      i = m_BeanInstancesID.indexOf(new Integer(id));
  
      // keep track of the BeanInstances for reading the connections later on
      m_BeanInstances.set(i, result);
  
      // no current MetaBean
      m_CurrentMetaBean = null;
      
      return result;
    }
  
    /**
     * adds the given BeanConncetion to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writeBeanConnection(Element parent, Object o, String name)
      throws Exception {
      
      Element           node;
      BeanConnection    beanconn;
      int               source;
      int               target;
      int               sourcePos;
      int               targetPos;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      beanconn = (BeanConnection) o;
      node     = null;
  
      // get position
      sourcePos = m_BeanInstances.indexOf(beanconn.getSource());
      targetPos = m_BeanInstances.indexOf(beanconn.getTarget());
      
      // get id (if Connection is from a Bean in the UserToolBar, it's not listed! -> ignore it)
      if ( (sourcePos > -1) && (targetPos > -1) ) {
        source = ((Integer) m_BeanInstancesID.get(sourcePos)).intValue();
        target = ((Integer) m_BeanInstancesID.get(targetPos)).intValue();
      }
      else {
         source = -1;
         target = -1;
      }
      
      // connection exists in the layout?
      if ( (source > -1) && (target > -1) ) {
        node = addElement(parent, name, beanconn.getClass().getName(), false);
    
        writeIntToXML(node, source, VAL_SOURCEID);
        writeIntToXML(node, target, VAL_TARGETID);
        invokeWriteToXML(node, beanconn.getEventName(), VAL_EVENTNAME);
        writeBooleanToXML(node, beanconn.isHidden(), VAL_HIDDEN);
      }
      
      return node;
    }
  
    /**
     * builds the BeanConnection from the given DOM node.
     * 
     * @param node the associated XML node
     * @return the instance created from the XML description
     * @throws Exception if instantiation fails
     */
    public Object readBeanConnection(Element node) throws Exception {
      Object                  result;
      Vector                  children;
      Element                 child;
      String                  name;
      int                     i;
      int                     source;
      int                     target;
      int                     sourcePos;
      int                     targetPos;
      String                  event;
      boolean                 hidden;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), node.getAttribute(ATT_NAME));
  
      m_CurrentNode = node;
      
      result   = null;
      children = XMLDocument.getChildTags(node);
      source   = 0;
      target   = 0;
      event    = "";
      hidden   = false;
  
      for (i = 0; i < children.size(); i++) {
        child = (Element) children.get(i);
        name  = child.getAttribute(ATT_NAME);
  
        if (name.equals(VAL_SOURCEID))
          source = readIntFromXML(child);
        else if (name.equals(VAL_TARGETID))
          target = readIntFromXML(child);
        else if (name.equals(VAL_EVENTNAME))
          event = (String) invokeReadFromXML(child);
        else if (name.equals(VAL_HIDDEN))
          hidden = readBooleanFromXML(child);
        else
          System.out.println("WARNING: '" + name
              + "' is not a recognized name for " + node.getAttribute(ATT_NAME) + "!");
      }
  
      // get position of id
      sourcePos = m_BeanInstancesID.indexOf(new Integer(source));
      targetPos = m_BeanInstancesID.indexOf(new Integer(target));
      
      // do we currently ignore the connections?
      // Note: necessary because of the MetaBeans
      if (m_IgnoreBeanConnections) {
        addBeanConnectionRelation(m_CurrentMetaBean, sourcePos + "," + targetPos + "," + event + "," + hidden);
        return result;
      }
  
      // generate it normally
      result = createBeanConnection(sourcePos, targetPos, event, hidden);
  
      return result;
    }
    
    /**
     * adds the given Loader (a bean) to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writeBeanLoader(Element parent, Object o, String name)
        throws Exception {
      
      Element                 node;
      weka.gui.beans.Loader   loader;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      loader = (weka.gui.beans.Loader) o;
      node   = addElement(parent, name, loader.getClass().getName(), false);
  
      invokeWriteToXML(node, loader.getLoader(), VAL_LOADER);
      invokeWriteToXML(node, loader.getBeanContext(), VAL_BEANCONTEXT);
      
      return node;
    }
  
    /**
     * adds the given Saver (a bean) to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writeBeanSaver(Element parent, Object o, String name)
        throws Exception {
      
      Element                 node;
      weka.gui.beans.Saver    saver;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      saver = (weka.gui.beans.Saver) o;
      node   = addElement(parent, name, saver.getClass().getName(), false);
      invokeWriteToXML(node, saver.getRelationNameForFilename(), VAL_RELATIONNAMEFORFILENAME);
  
      invokeWriteToXML(node, saver.getSaverTemplate(), VAL_SAVER);
      
      return node;
    }
    
    /**
     * adds the given Loader to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writeLoader(Element parent, Object o, String name)
        throws Exception {
      
      Element                       node;
      weka.core.converters.Loader   loader;
      File                          file;
      boolean                       known;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      loader = (weka.core.converters.Loader) o;
      node   = addElement(parent, name, loader.getClass().getName(), false);
      known  = true;
      file   = null;
  
      // file
      if (loader instanceof weka.core.converters.AbstractFileLoader)
        file = ((weka.core.converters.AbstractFileLoader) loader).retrieveFile();
      else
        known = false;
  
      if (!known)
        System.out.println("WARNING: unknown loader class '" + loader.getClass().getName() + "' - cannot retrieve file!");
  
      Boolean relativeB = null;
      if (loader instanceof weka.core.converters.FileSourcedConverter) {
        boolean relative = ((weka.core.converters.FileSourcedConverter)loader).getUseRelativePath();
        relativeB = new Boolean(relative);
      }
      
      // only save it, if it's a real file!
      if ( (file == null) || (file.isDirectory()) ) {
        invokeWriteToXML(node, "", VAL_FILE);
      } else {
        String withResourceSeparators = file.getPath().replace(File.pathSeparatorChar, '/');
        boolean notAbsolute = 
          (((weka.core.converters.AbstractFileLoader) loader).getUseRelativePath() ||
          (loader instanceof EnvironmentHandler 
              && Environment.containsEnvVariables(file.getPath())) ||
              this.getClass().getClassLoader().getResource(withResourceSeparators) != null ||
              !file.exists());
        
        String path = (notAbsolute)
          ? file.getPath()
          : file.getAbsolutePath();
        // Replace any windows file separators with forward slashes (Java under windows can
        // read paths with forward slashes (apparantly)
        path = path.replace('\\', '/');
        invokeWriteToXML(node, path, VAL_FILE);
      }
      if (relativeB != null) {
        invokeWriteToXML(node, relativeB.toString(), VAL_RELATIVE_PATH);
      }
      
      if (loader instanceof weka.core.OptionHandler) {
        String[] opts = ((weka.core.OptionHandler)loader).getOptions();
        invokeWriteToXML(node, opts, VAL_OPTIONS);
      }
      
      return node;
    }
  
    /**
     * builds the Loader from the given DOM node.
     * 
     * @param node the associated XML node
     * @return the instance created from the XML description
     * @throws Exception if instantiation fails
     */
    public Object readLoader(Element node) throws Exception {
      Object      result;
      Vector      children;
      Element     child;
      int         i;
      String      name;
      String      file;
      File        fl;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), node.getAttribute(ATT_NAME));
  
      m_CurrentNode = node;
      
      result   = Class.forName(node.getAttribute(ATT_CLASS)).newInstance();
      children = XMLDocument.getChildTags(node);
      file     = "";
      Object relativeB = null;
      boolean relative = false;
  
      for (i = 0; i < children.size(); i++) {
        child = (Element) children.get(i);
        name  = child.getAttribute(ATT_NAME);
  
        if (name.equals(VAL_FILE)) {
          file = (String) invokeReadFromXML(child);
        } else if (name.equals(VAL_RELATIVE_PATH)) {
          relativeB = readFromXML(child);
          if (relativeB instanceof Boolean) {
            relative = ((Boolean)relativeB).booleanValue();
          }
        } else {
          readFromXML(result, name, child);
        }
      }
  
      if (result instanceof weka.core.converters.FileSourcedConverter) {
        ((weka.core.converters.FileSourcedConverter)result).setUseRelativePath(relative);
      }
  
      if (file.equals(""))
        file = null;
  
      // set file only, if it exists
      if (file != null) {
        String tempFile = file;
  
        boolean containsEnv = false;
        containsEnv = Environment.containsEnvVariables(file);
        
        fl = new File(file);      
        // only test for existence if the path does not contain environment vars
        // (trust that after they are resolved that everything is hunky dory). Also 
        // don't test if the file can be found as a resource in the classath
        if (containsEnv || fl.exists() || 
            this.getClass().getClassLoader().getResource(file) != null) {
          ((weka.core.converters.AbstractFileLoader) result).setSource(new File(file));
        } else {
          System.out.println("WARNING: The file '" + tempFile + "' does not exist!");
        }
      }
      
      return result;
    }
  
    /**
     * adds the given Saver to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writeSaver(Element parent, Object o, String name)
        throws Exception {
      
      Element                     node;
      weka.core.converters.Saver  saver;
      File                        file;
      String			prefix;
      String			dir;
      boolean                     known;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      saver  = (weka.core.converters.Saver) o;
      node   = addElement(parent, name, saver.getClass().getName(), false);
      known  = true;
      file   = null;
      prefix = "";
      dir    = "";
  
      // file
      if (saver instanceof weka.core.converters.AbstractFileSaver) {
        file   = ((weka.core.converters.AbstractFileSaver) saver).retrieveFile();
        prefix = ((weka.core.converters.AbstractFileSaver) saver).filePrefix();
        dir    = ((weka.core.converters.AbstractFileSaver) saver).retrieveDir();
        // Replace any windows file separators with forward slashes (Java under windows can
        // read paths with forward slashes (apparantly)
        dir = dir.replace('\\', '/');
      }
      else {
        known = false;
      }
      
      if (!known)
        System.out.println("WARNING: unknown saver class '" + saver.getClass().getName() + "' - cannot retrieve file!");
  
      Boolean relativeB = null;
      if (saver instanceof weka.core.converters.FileSourcedConverter) {
        boolean relative = ((weka.core.converters.FileSourcedConverter)saver).getUseRelativePath();
        relativeB = new Boolean(relative);
      }
      
  
  //    if ( (file == null) || (file.isDirectory()) ) {
        invokeWriteToXML(node, "",     VAL_FILE);
        invokeWriteToXML(node, dir,    VAL_DIR);
        invokeWriteToXML(node, prefix, VAL_PREFIX);
  /*    }
      else {
        String path = (((weka.core.converters.FileSourcedConverter) saver).getUseRelativePath())
          ? file.getPath()
          : file.getAbsolutePath();
        // Replace any windows file separators with forward slashes (Java under windows can
        // read paths with forward slashes (apparantly)
        path = path.replace('\\', '/');
        invokeWriteToXML(node, path, VAL_FILE);
        invokeWriteToXML(node, dir, VAL_DIR);
        invokeWriteToXML(node, prefix, VAL_PREFIX);
      }*/
  
      if (relativeB != null) {
        invokeWriteToXML(node, relativeB.toString(), VAL_RELATIVE_PATH);
      }
      
      if (saver instanceof weka.core.OptionHandler) {
        String[] opts = ((weka.core.OptionHandler)saver).getOptions();
        invokeWriteToXML(node, opts, VAL_OPTIONS);
      }
      
      return node;
    }
  
    /**
     * builds the Saver from the given DOM node.
     * 
     * @param node the associated XML node
     * @return the instance created from the XML description
     * @throws Exception if instantiation fails
     */
    public Object readSaver(Element node) throws Exception {
      Object      result;
      Vector      children;
      Element     child;
      int         i;
      String      name;
      String      file;
      String	dir;
      String	prefix;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), node.getAttribute(ATT_NAME));
  
      m_CurrentNode = node;
      
      result   = Class.forName(node.getAttribute(ATT_CLASS)).newInstance();
      children = XMLDocument.getChildTags(node);
      file     = null;
      dir      = null;
      prefix   = null;
  
      Object relativeB = null;
      boolean relative = false;
  
      for (i = 0; i < children.size(); i++) {
        child = (Element) children.get(i);
        name  = child.getAttribute(ATT_NAME);
  
        if (name.equals(VAL_FILE)) {
          file = (String) invokeReadFromXML(child);
        } else if (name.equals(VAL_DIR)) {
          dir = (String) invokeReadFromXML(child);
        } else if (name.equals(VAL_PREFIX)) {
          prefix = (String) invokeReadFromXML(child);
        } else if (name.equals(VAL_RELATIVE_PATH)) {
          relativeB = readFromXML(child);
          if (relativeB instanceof Boolean) {
            relative = ((Boolean)relativeB).booleanValue();
          }
        } else {
          readFromXML(result, name, child);
        }
      }
  
      if ( (file != null) && (file.length() == 0) )
        file = null;
  
      // savers only get directory and prefix, not file (KnowledgeFlow sets the
      // file/destination based on the relation, dir and prefix)
      if ( (dir != null) && (prefix != null) ) {
        ((weka.core.converters.AbstractFileSaver) result).setDir(dir);
        ((weka.core.converters.AbstractFileSaver) result).setFilePrefix(prefix);
      }
  
      if (result instanceof weka.core.converters.FileSourcedConverter) {
        ((weka.core.converters.FileSourcedConverter)result).setUseRelativePath(relative);
      }
      
      return result;
    }
  
    /**
     * adds the given BeanVisual to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writeBeanVisual(Element parent, Object o, String name)
        throws Exception {
      
      Element         node;
      BeanVisual      visual;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      visual = (BeanVisual) o;
      node   = writeToXML(parent, o, name);
  
      // add icon paths
      invokeWriteToXML(node, visual.getIconPath(),         VAL_ICONPATH);
      invokeWriteToXML(node, visual.getAnimatedIconPath(), VAL_ANIMATEDICONPATH);
      
      return node;
    }
  
    /**
     * builds the BeanVisual from the given DOM node.
     * 
     * @param node the associated XML node
     * @return the instance created from the XML description
     * @throws Exception if instantiation fails
     */
    public Object readBeanVisual(Element node) throws Exception {
      Object      result;
      Vector      children;
      Element     child;
      int         i;
      String      name;
      String      text;
      String      iconPath;
      String      animIconPath;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), node.getAttribute(ATT_NAME));
  
      m_CurrentNode = node;
      
      result       = null;
      children     = XMLDocument.getChildTags(node);
      text         = "";
      iconPath     = "";
      animIconPath = "";
  
      // find text
      for (i = 0; i < children.size(); i++) {
        child = (Element) children.get(i);
        name  = child.getAttribute(ATT_NAME);
  
        if (name.equals(VAL_TEXT))
          text = (String) invokeReadFromXML(child);
        else if (name.equals(VAL_ICONPATH))
          iconPath = (String) invokeReadFromXML(child);
        else if (name.equals(VAL_ANIMATEDICONPATH))
          animIconPath = (String) invokeReadFromXML(child);
      }
  
      result = new BeanVisual(text, iconPath, animIconPath);
      
      // set rest of properties
      for (i = 0; i < children.size(); i++)
        readFromXML(result, node.getAttribute(ATT_NAME), (Element) children.get(i));
      
      return result;
    }
    
    /**
     * returns the IDs for the given BeanInstances, i.e., the stored IDs
     * in m_BeanInstancesID, based on m_BeanInstances
     * 
     * @param beans       the beans to retrieve the IDs for
     * @return            the IDs for the given BeanInstances
     * @see #m_BeanInstances
     * @see #m_BeanInstancesID
     */
    protected Vector getIDsForBeanInstances(Vector beans) {
      Vector        result;
      int           i;
      int           pos;
      
      result = new Vector();
  
      for (i = 0; i < beans.size(); i++) {
        pos = m_BeanInstances.indexOf(beans.get(i));
        result.add(m_BeanInstancesID.get(pos));
      }
      
      return result;
    }
    
    /**
     * adds the given MetaBean to a DOM structure.
     * 
     * @param parent the parent of this object, e.g. the class this object is a member of
     * @param o the Object to describe in XML
     * @param name the name of the object
     * @return the node that was created
     * @throws Exception if the DOM creation fails
     */
    public Element writeMetaBean(Element parent, Object o, String name)
        throws Exception {
      
      Element         node;
      MetaBean        meta;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), name);
      
      m_CurrentNode = parent;
      
      meta = (MetaBean) o;
      node = writeToXML(parent, o, name);
  
      invokeWriteToXML(node, getIDsForBeanInstances(meta.getBeansInInputs()), VAL_INPUTSID);
      invokeWriteToXML(node, getIDsForBeanInstances(meta.getBeansInOutputs()), VAL_OUTPUTSID);
      
      return node;
    }
    
    /**
     * returns a vector with references to BeanInstances according to the IDs
     * in the given Vector.
     * @param ids       contains the IDs of the BeanInstances
     * @return          the corresponding BeanInstances
     * @see #m_BeanInstances
     * @see #m_BeanInstancesID
     */
    protected Vector getBeanInstancesForIDs(Vector ids) {
      Vector        result;
      int           i;
      int           pos;
      
      result = new Vector();
      
      for (i = 0; i < ids.size(); i++) {
        pos = m_BeanInstancesID.indexOf(ids.get(i));
        result.add(m_BeanInstances.get(pos));
      }
      
      return result;
    }
  
    /**
     * builds the MetaBean from the given DOM node.
     * 
     * @param node the associated XML node
     * @return the instance created from the XML description
     * @throws Exception if instantiation fails
     */
    public Object readMetaBean(Element node) throws Exception {
      Object          result;
      Vector          children;
      Element         child;
      int             i;
      String          name;
      Vector          inputs;
      Vector          outputs;
      Vector          coords;
      MetaBean        bean;
  
      // for debugging only
      if (DEBUG)
         trace(new Throwable(), node.getAttribute(ATT_NAME));
  
      m_CurrentNode = node;
      
      result   = new MetaBean();
      children = XMLDocument.getChildTags(node);
      inputs   = new Vector();
      outputs  = new Vector();
      coords   = new Vector();
      
      // the current MetaBean
      m_CurrentMetaBean = (MetaBean) result;
  
      for (i = 0; i < children.size(); i++) {
        child = (Element) children.get(i);
        name  = child.getAttribute(ATT_NAME);
  
        if (name.equals(VAL_ASSOCIATEDCONNECTIONS))
          ((MetaBean) result).setAssociatedConnections((Vector) invokeReadFromXML(child));
        else if (name.equals(VAL_INPUTSID))
          inputs = (Vector) invokeReadFromXML(child);
        else if (name.equals(VAL_OUTPUTSID))
          outputs = (Vector) invokeReadFromXML(child);
        else if (name.equals(VAL_SUBFLOW))
          ((MetaBean) result).setSubFlow((Vector) invokeReadFromXML(child));
        else if (name.equals(VAL_ORIGINALCOORDS))
          coords = (Vector) invokeReadFromXML(child);
        else if (name.equals(VAL_INPUTS))
          System.out.println("INFO: '" + name + "' will be restored later.");
        else if (name.equals(VAL_OUTPUTS))
          System.out.println("INFO: '" + name + "' will be restored later.");
        else
          readFromXML(result, name, child);
      }
  
      bean = (MetaBean) result;
      
      // set inputs and outputs, after the beans have been instantiated
      bean.setInputs(getBeanInstancesForIDs(inputs));
      bean.setOutputs(getBeanInstancesForIDs(outputs));
      bean.setOriginalCoords(coords);
      
      return result;
    }
  }
  