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.
   */
  
  /*
   *    Saver.java
   *    Copyright (C) 2004 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.gui.beans;
  
  import java.io.IOException;
  import java.io.ObjectInputStream;
  
  import weka.core.Environment;
  import weka.core.EnvironmentHandler;
  import weka.core.Instances;
  import weka.core.OptionHandler;
  import weka.core.SerializedObject;
  import weka.core.Utils;
  import weka.core.converters.ArffSaver;
  import weka.core.converters.DatabaseConverter;
  import weka.core.converters.DatabaseSaver;
  
  /**
   * Saves data sets using weka.core.converter classes
   *
   * @author <a href="mailto:mutter@cs.waikato.ac.nz">Stefan Mutter</a>
   * @version $Revision: 7707 $
   *
   */
  public class Saver
    extends AbstractDataSink
    implements WekaWrapper, EnvironmentHandler {
  
    /** for serialization */
    private static final long serialVersionUID = 5371716690308950755L;
  
    /**
     * Holds the instances to be saved
     */
    private Instances m_dataSet;
  
    /**
     * Holds the structure
     */
    private Instances m_structure;
  
    /**
     * Global info for the wrapped loader (if it exists).
     */
    protected String m_globalInfo;
  
    /**
     * Thread for doing IO in
     */
    private transient SaveBatchThread m_ioThread;
  
    /**
     * Saver
     */
    private weka.core.converters.Saver m_Saver= new ArffSaver();
    private weka.core.converters.Saver m_SaverTemplate = m_Saver;
  
    /**
     * The relation name that becomes part of the file name
     */
    private String m_fileName;
    
    /** Flag indicating that instances will be saved to database. Used because structure information can only be sent after a database has been configured.*/
    private boolean m_isDBSaver;
    
    /** 
     * For file-based savers - if true (default), relation name is used
     * as the primary part of the filename. If false, then the prefix is
     * used as the filename. Useful for preventing filenames from getting
     * too long when there are many filters in a flow. 
     */
    private boolean m_relationNameForFilename = true;
   
    /**
     * Count for structure available messages
     */
    private int m_count;
    
    /**
     * The environment variables.
     */
    protected transient Environment m_env;
    
    private weka.core.converters.Saver makeCopy() throws Exception {
      return (weka.core.converters.Saver)new SerializedObject(m_SaverTemplate).getObject();
    }
    
    private class SaveBatchThread extends Thread {
      private DataSink m_DS;
  
      public SaveBatchThread(DataSink ds) {
        m_DS= ds;
      }
  
      public void run() {
        try {
          m_visual.setAnimated();
                  
          m_Saver.setInstances(m_dataSet);
          if (m_logger != null) {
            m_logger.statusMessage(statusMessagePrefix() + "Saving "
                + m_dataSet.relationName() + "...");
          }
          m_Saver.writeBatch();
          if (m_logger != null) {
            m_logger.logMessage("[Saver] " + statusMessagePrefix() 
                + "Save successful.");
          }
  	
        } catch (Exception ex) {
          if (m_logger != null) {
            m_logger.statusMessage(statusMessagePrefix()
                + "ERROR (See log for details)");
            m_logger.logMessage("[Saver] " + statusMessagePrefix()
                + " problem saving. " 
                + ex.getMessage());
          }
  	ex.printStackTrace();
        } finally {
          if (Thread.currentThread().isInterrupted()) {
            if (m_logger != null) {
              m_logger.logMessage("[Saver] " + statusMessagePrefix()
                  + " Saving interrupted!!");
            }
          }
          if (m_logger != null) {
            m_logger.statusMessage(statusMessagePrefix() + "Finished.");
          }
          block(false);
  	m_visual.setStatic();
  	m_ioThread = null;
        }
      }
    }
    
    /**
     * Function used to stop code that calls acceptTrainingSet. This is 
     * needed as classifier construction is performed inside a separate
     * thread of execution.
     *
     * @param tf a <code>boolean</code> value
     */
    private synchronized void block(boolean tf) {
  
      if (tf) {
        try {
  	if (m_ioThread.isAlive()) {
  	  wait();
  	  }
        } catch (InterruptedException ex) {
        }
      } else {
        notifyAll();
      }
    }
    
    /**
     * Returns true if. at this time, the bean is busy with some
     * (i.e. perhaps a worker thread is performing some calculation).
     * 
     * @return true if the bean is busy.
     */
    public boolean isBusy() {
      return (m_ioThread != null);
    }
  
    /**
     * Global info (if it exists) for the wrapped loader
     *
     * @return the global info
     */
    public String globalInfo() {
      return m_globalInfo;
    }
  
    /** Contsructor */  
    public Saver() {
      super();
      setSaverTemplate(m_Saver);
      m_fileName = "";
      m_dataSet = null;
      m_count = 0;
      
    }
  
    /**
     * Set a custom (descriptive) name for this bean
     * 
     * @param name the name to use
     */
    public void setCustomName(String name) {
      m_visual.setText(name);
    }
  
    /**
     * Get the custom (descriptive) name for this bean (if one has been set)
     * 
     * @return the custom name (or the default name)
     */
    public String getCustomName() {
      return m_visual.getText();
    }  
    
    /**
     * Set environment variables to use.
     * 
     * @param env the environment variables to
     * use
     */
    public void setEnvironment(Environment env) {
      m_env = env;
    }
    
    /**
     * Pass the environment variables on the the wrapped saver
     */
    private void passEnvOnToSaver() {
      // set environment variables
      if (m_SaverTemplate instanceof EnvironmentHandler && m_env != null) {
        ((EnvironmentHandler)m_Saver).setEnvironment(m_env);
      }
    }
  
    /** Set the loader to use
     * @param saver a Saver
     */
    public void setSaverTemplate(weka.core.converters.Saver saver) {
      boolean loadImages = true;
      if (saver.getClass().getName().
  	compareTo(m_SaverTemplate.getClass().getName()) == 0) {
        loadImages = false;
      }
      m_SaverTemplate = saver;
      String saverName = saver.getClass().toString();
      saverName = saverName.substring(saverName.
  				      lastIndexOf('.')+1, 
  				      saverName.length());
      if (loadImages) {
  
        if (!m_visual.loadIcons(BeanVisual.ICON_PATH+saverName+".gif",
  			    BeanVisual.ICON_PATH+saverName+"_animated.gif")) {
  	useDefaultVisual();
        }
      }
      m_visual.setText(saverName);
  
      
      // get global info
      m_globalInfo = KnowledgeFlowApp.getGlobalInfo(m_SaverTemplate);
      if(m_SaverTemplate instanceof DatabaseConverter)
          m_isDBSaver = true;
      else
          m_isDBSaver = false;
    }
  
    /**
     * makes sure that the filename is valid, i.e., replaces slashes,
     * backslashes and colons with underscores ("_"). Also try to prevent
     * filename from becoming insanely long by removing package part
     * of class names.
     * 
     * @param filename	the filename to cleanse
     * @return		the cleansed filename
     */
    protected String sanitizeFilename(String filename) {
      filename = filename.replaceAll("\\\\", "_").replaceAll(":", "_").replaceAll("/", "_");
      filename = Utils.removeSubstring(filename, "weka.filters.supervised.instance.");
      filename = Utils.removeSubstring(filename, "weka.filters.supervised.attribute.");
      filename = Utils.removeSubstring(filename, "weka.filters.unsupervised.instance.");
      filename = Utils.removeSubstring(filename, "weka.filters.unsupervised.attribute.");
      filename = Utils.removeSubstring(filename, "weka.clusterers.");
      filename = Utils.removeSubstring(filename, "weka.associations.");
      filename = Utils.removeSubstring(filename, "weka.attributeSelection.");
      filename = Utils.removeSubstring(filename, "weka.estimators.");
      filename = Utils.removeSubstring(filename, "weka.datagenerators.");
      
      if (!m_isDBSaver && !m_relationNameForFilename) {
        filename = "";
        try {
          if (m_Saver.filePrefix().equals("")) {
            m_Saver.setFilePrefix("no-name");
          }
        } catch (Exception ex) {
          System.err.println(ex);
        }
      }
  
      return filename;
    }
    
    /** Method reacts to a dataset event and starts the writing process in batch mode
     * @param e a dataset event
     */  
    public synchronized void acceptDataSet(DataSetEvent e) {
    
        try {
          m_Saver = makeCopy();
        } catch (Exception ex) {
          if (m_logger != null) {
            m_logger.statusMessage(statusMessagePrefix()
                + "ERROR (See log for details)");
            m_logger.logMessage("[Saver] " + statusMessagePrefix()
                + " unable to copy saver. " 
                + ex.getMessage());
          }
        }
        passEnvOnToSaver();
        m_fileName = sanitizeFilename(e.getDataSet().relationName());
        m_dataSet = e.getDataSet();
        if(e.isStructureOnly() && m_isDBSaver && ((DatabaseSaver)m_SaverTemplate).getRelationForTableName()){//
            ((DatabaseSaver)m_Saver).setTableName(m_fileName);
        }
        if(!e.isStructureOnly()){
            if(!m_isDBSaver){
              try{
                  m_Saver.setDirAndPrefix(m_fileName,"");
              }catch (Exception ex){
                  System.out.println(ex);
              }
            }
            saveBatch();
            System.out.println("...relation "+ m_fileName +" saved.");
        }
    }
    
    /**
     * Method reacts to a threshold data event ans starts the writing process
     * in batch mode.
     * 
     * @param e threshold data event.
     */
    public synchronized void acceptDataSet(ThresholdDataEvent e) {
      try {
        m_Saver = makeCopy();
      } catch (Exception ex) {
        if (m_logger != null) {
          m_logger.statusMessage(statusMessagePrefix()
              + "ERROR (See log for details)");
          m_logger.logMessage("[Saver] " + statusMessagePrefix()
              + " unable to copy saver. " 
              + ex.getMessage());
        }
      }
      
      passEnvOnToSaver();
      m_fileName = sanitizeFilename(e.getDataSet().getPlotInstances().relationName());
      m_dataSet = e.getDataSet().getPlotInstances();
      
      if(m_isDBSaver && ((DatabaseSaver)m_SaverTemplate).getRelationForTableName()){//
        ((DatabaseSaver)m_Saver).setTableName(m_fileName);
        ((DatabaseSaver)m_Saver).setRelationForTableName(false);
      }
  
      if(!m_isDBSaver){
        try{
          m_Saver.setDirAndPrefix(m_fileName,"");
        }catch (Exception ex){
          System.out.println(ex);
        }
      }
      saveBatch();
      System.out.println("...relation "+ m_fileName +" saved.");
    }
    
    /** Method reacts to a test set event and starts the writing process in batch mode
     * @param e test set event
     */  
    public synchronized void acceptTestSet(TestSetEvent e) {
        if (e.isStructureOnly()) {
          try {
            m_Saver = makeCopy();
          } catch (Exception ex) {
            if (m_logger != null) {
              m_logger.statusMessage(statusMessagePrefix()
                  + "ERROR (See log for details)");
              m_logger.logMessage("[Saver] " + statusMessagePrefix()
                  + " unable to copy saver. " 
                  + ex.getMessage());
            }
          }
        }
        passEnvOnToSaver();
        m_fileName = sanitizeFilename(e.getTestSet().relationName());
        m_dataSet = e.getTestSet();
        if(e.isStructureOnly() && m_isDBSaver && ((DatabaseSaver)m_SaverTemplate).getRelationForTableName()){
            ((DatabaseSaver)m_Saver).setTableName(m_fileName);
        }
        if(!e.isStructureOnly()){
            if(!m_isDBSaver){
              try{
                  m_Saver.setDirAndPrefix(m_fileName,"_test_"+e.getSetNumber()+"_of_"+e.getMaxSetNumber());
              }catch (Exception ex){
                  System.out.println(ex);
              }
            }
            else{
                ((DatabaseSaver)m_Saver).setRelationForTableName(false);
                String setName = ((DatabaseSaver)m_Saver).getTableName();
                setName = setName.replaceFirst("_[tT][eE][sS][tT]_[0-9]+_[oO][fF]_[0-9]+","");
                ((DatabaseSaver)m_Saver).setTableName(setName+"_test_"+e.getSetNumber()+"_of_"+e.getMaxSetNumber());
            }
            saveBatch();
            System.out.println("... test set "+e.getSetNumber()+" of "+e.getMaxSetNumber()+" for relation "+ m_fileName +" saved.");
        }
    }
    
    /** Method reacts to a training set event and starts the writing process in batch
     * mode
     * @param e a training set event
     */  
    public synchronized void acceptTrainingSet(TrainingSetEvent e) {
      if (e.isStructureOnly()) {
        try {
          m_Saver = makeCopy();
        } catch (Exception ex) {
          if (m_logger != null) {
            m_logger.statusMessage(statusMessagePrefix()
                + "ERROR (See log for details)");
            m_logger.logMessage("[Saver] " + statusMessagePrefix()
                + " unable to copy saver. " 
                + ex.getMessage());
          }
        }
      }
    
        passEnvOnToSaver();
        m_fileName = sanitizeFilename(e.getTrainingSet().relationName());
        m_dataSet = e.getTrainingSet();
        if(e.isStructureOnly() && m_isDBSaver && ((DatabaseSaver)m_SaverTemplate).getRelationForTableName()){
             ((DatabaseSaver)m_Saver).setTableName(m_fileName);
        }
        if(!e.isStructureOnly()){
            if(!m_isDBSaver){
              try{
                  m_Saver.setDirAndPrefix(m_fileName,"_training_"+e.getSetNumber()+"_of_"+e.getMaxSetNumber());
              }catch (Exception ex){
                  System.out.println(ex);
              }
            }
            else{
              ((DatabaseSaver)m_Saver).setRelationForTableName(false);
                String setName = ((DatabaseSaver)m_Saver).getTableName();
                setName = setName.replaceFirst("_[tT][rR][aA][iI][nN][iI][nN][gG]_[0-9]+_[oO][fF]_[0-9]+","");
                ((DatabaseSaver)m_Saver).setTableName(setName+"_training_"+e.getSetNumber()+"_of_"+e.getMaxSetNumber());
            }
            saveBatch();
            System.out.println("... training set "+e.getSetNumber()+" of "+e.getMaxSetNumber()+" for relation "+ m_fileName +" saved.");
        }
    }
    
    /** Saves instances in batch mode */  
    public synchronized void saveBatch(){
    
        m_Saver.setRetrieval(m_Saver.BATCH);
  /*      String visText = this.getName();
        try {
          visText = (m_fileName.length() > 0) ? m_fileName : m_Saver.filePrefix();
        } catch (Exception ex) {        
        }
        m_visual.setText(visText); */
        m_ioThread = new SaveBatchThread(Saver.this);
        m_ioThread.setPriority(Thread.MIN_PRIORITY);
        m_ioThread.start();
        block(true);
    }
    
    /** Methods reacts to instance events and saves instances incrementally.
     * If the instance to save is null, the file is closed and the saving process is
     * ended.
     * @param e instance event
     */  
    public synchronized void acceptInstance(InstanceEvent e) {
        
        
        if(e.getStatus() == e.FORMAT_AVAILABLE){
          // start of a new stream
          try {
            m_Saver = makeCopy();
          } catch (Exception ex) {
            if (m_logger != null) {
              m_logger.statusMessage(statusMessagePrefix()
                  + "ERROR (See log for details)");
              m_logger.logMessage("[Saver] " + statusMessagePrefix()
                  + " unable to copy saver. " 
                  + ex.getMessage());
            }
          }
          m_Saver.setRetrieval(m_Saver.INCREMENTAL);
          m_structure = e.getStructure();
          m_fileName = sanitizeFilename(m_structure.relationName());
          m_Saver.setInstances(m_structure);
          if(m_isDBSaver)
              if(((DatabaseSaver)m_SaverTemplate).getRelationForTableName()) {
                  ((DatabaseSaver)m_Saver).setTableName(m_fileName);
                  ((DatabaseSaver)m_Saver).setRelationForTableName(false);
              }
        }
        if(e.getStatus() == e.INSTANCE_AVAILABLE){
          m_visual.setAnimated();
          if(m_count == 0){
              passEnvOnToSaver();
              if(!m_isDBSaver){
                  try{
                      m_Saver.setDirAndPrefix(m_fileName,"");
                  }catch (Exception ex){
                      System.out.println(ex);
                      m_visual.setStatic();
                  }
              }
              m_count ++;
          }
          try{
  /*          String visText = this.getName();
            visText = (m_fileName.length() > 0) ? m_fileName : m_Saver.filePrefix();
              m_visual.setText(m_fileName); */
              m_Saver.writeIncremental(e.getInstance());
          } catch (Exception ex) {
              m_visual.setStatic();
              System.err.println("Instance "+e.getInstance() +" could not been saved");
              ex.printStackTrace();
          }
        }
        if(e.getStatus() == e.BATCH_FINISHED){
          try{  
              m_Saver.writeIncremental(e.getInstance());
              m_Saver.writeIncremental(null);
              //m_firstNotice = true;
              m_visual.setStatic();
              System.out.println("...relation "+ m_fileName +" saved.");
  /*            String visText = this.getName();
              visText = (m_fileName.length() > 0) ? m_fileName : m_Saver.filePrefix();
              m_visual.setText(visText); */     
              m_count = 0;
          } catch (Exception ex) {
              m_visual.setStatic();
              System.err.println("File could not have been closed.");
              ex.printStackTrace();
          }
        }
    }
    
    
  
    /**
     * Get the saver
     *
     * @return a <code>weka.core.converters.Saver</code> value
     */
    public weka.core.converters.Saver getSaverTemplate() {
      return m_SaverTemplate;
    }
  
    /**
     * Set the saver
     *
     * @param algorithm a Saver
     */
    public void setWrappedAlgorithm(Object algorithm) 
      {
      if (!(algorithm instanceof weka.core.converters.Saver)) { 
        throw new IllegalArgumentException(algorithm.getClass()+" : incorrect "
  					 +"type of algorithm (Loader)");
      }
      setSaverTemplate((weka.core.converters.Saver)algorithm);
    }
  
    /**
     * Get the saver
     *
     * @return a Saver
     */
    public Object getWrappedAlgorithm() {
      return getSaverTemplate();
    }
    
    /**
     * Set whether to use the relation name as the primary part
     * of the filename. If false, then the prefix becomes the filename.
     * 
     * @param r true if the relation name is to be part of the filename.
     */
    public void setRelationNameForFilename(boolean r) {
      m_relationNameForFilename = r;
    }
    
    /**
     * Get whether the relation name is the primary part of the filename.
     * 
     * @return true if the relation name is part of the filename.
     */
    public boolean getRelationNameForFilename() {
      return m_relationNameForFilename;
    }
  
    /** Stops the bean */  
    public void stop() {
      // tell the listenee (upstream bean) to stop
      if (m_listenee instanceof BeanCommon) {
        ((BeanCommon)m_listenee).stop();
      }
      
      // stop the io thread
      if (m_ioThread != null) {
        m_ioThread.interrupt();
        m_ioThread.stop();
        m_ioThread = null;
        m_visual.setStatic();
      }
    }
    
    private String statusMessagePrefix() {
      return getCustomName() + "$" + hashCode() + "|"
      + ((m_Saver instanceof OptionHandler) 
          ? Utils.joinOptions(((OptionHandler)m_Saver).getOptions()) + "|"
              : "");
    }
    
    // Custom de-serialization in order to set default
    // environment variables on de-serialization
    private void readObject(ObjectInputStream aStream) 
      throws IOException, ClassNotFoundException {
      aStream.defaultReadObject();
      
      // set a default environment to use
      m_env = Environment.getSystemWide();
    }
    
    
    /** The main method for testing
     * @param args
     */  
    public static void main(String [] args) {
      try {
        final javax.swing.JFrame jf = new javax.swing.JFrame();
        jf.getContentPane().setLayout(new java.awt.BorderLayout());
  
        final Saver tv = new Saver();
  
        jf.getContentPane().add(tv, java.awt.BorderLayout.CENTER);
        jf.addWindowListener(new java.awt.event.WindowAdapter() {
          public void windowClosing(java.awt.event.WindowEvent e) {
            jf.dispose();
            System.exit(0);
          }
        });
        jf.setSize(800,600);
        jf.setVisible(true);
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }
    
  }
  
  