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.
   */
  
  /*
   *    ClassAssigner.java
   *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.gui.beans;
  
  import weka.core.Attribute;
  import weka.core.Instances;
  
  import java.awt.BorderLayout;
  import java.beans.EventSetDescriptor;
  import java.io.Serializable;
  import java.util.Vector;
  
  import javax.swing.JPanel;
  
  /**
   * Bean that assigns a class attribute to a data set.
   *
   * @author Mark Hall
   * @version $Revision: 7428 $
   */
  public class ClassAssigner
    extends JPanel
    implements Visible, DataSourceListener, TrainingSetListener, TestSetListener,
  	     DataSource, TrainingSetProducer, TestSetProducer,
  	     BeanCommon, EventConstraints, Serializable,
  	     InstanceListener, StructureProducer {
  
    /** for serialization */
    private static final long serialVersionUID = 4011131665025817924L;
    
    private String m_classColumn = "last";
  
    /** format of instances for current incoming connection (if any) */
    private Instances m_connectedFormat;
  
    private Object m_trainingProvider;
    private Object m_testProvider;
    private Object m_dataProvider;
    private Object m_instanceProvider;
  
    private Vector m_trainingListeners = new Vector();
    private Vector m_testListeners = new Vector();
    private Vector m_dataListeners = new Vector();
    private Vector m_instanceListeners = new Vector();
  
    private Vector m_dataFormatListeners = new Vector();
  
    protected transient weka.gui.Logger m_logger = null;
  
    protected BeanVisual m_visual = 
      new BeanVisual("ClassAssigner", 
  		   BeanVisual.ICON_PATH+"ClassAssigner.gif",
  		   BeanVisual.ICON_PATH+"ClassAssigner_animated.gif");
    
    /**
     * Global info for this bean
     *
     * @return a <code>String</code> value
     */
    public String globalInfo() {
      return "Designate which column is to be considered the class column "
        +"in incoming data.";
    }
  
    public ClassAssigner() {
      setLayout(new BorderLayout());
      add(m_visual, BorderLayout.CENTER);    
    }
  
    /**
     * 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();
    }
  
    /**
     * Tool tip text for this property
     *
     * @return a <code>String</code> value
     */
    public String classColumnTipText() {
      return "Specify the number of the column that contains the class attribute";
    }
    
    private Instances getUpstreamStructure() {
      if (m_dataProvider != null && m_dataProvider instanceof StructureProducer) {
        return ((StructureProducer)m_dataProvider).getStructure("dataSet");
      }
      if (m_trainingProvider != null && 
          m_trainingProvider instanceof StructureProducer) {
        return ((StructureProducer)m_trainingProvider).getStructure("trainingSet");
      }
      if (m_testProvider != null && m_testProvider instanceof StructureProducer) {
        return ((StructureProducer)m_testProvider).getStructure("testSet");
      }
      if (m_instanceProvider != null && 
          m_instanceProvider instanceof StructureProducer) {
        return ((StructureProducer)m_instanceProvider).getStructure("instance");
      }
      return null;
    }
    
    /**
     * Get the structure of the output encapsulated in the named
     * event. If the structure can't be determined in advance of
     * seeing input, or this StructureProducer does not generate
     * the named event, null should be returned.
     * 
     * @param eventName the name of the output event that encapsulates
     * the requested output.
     * 
     * @return the structure of the output encapsulated in the named
     * event or null if it can't be determined in advance of seeing input
     * or the named event is not generated by this StructureProduce.
     */
    public Instances getStructure(String eventName) {
      if (!eventName.equals("trainingSet") && !eventName.equals("testSet") && 
          !eventName.equals("dataSet") && !eventName.equals("instance")) {
        return null;
      }
      if (m_trainingProvider == null && m_testProvider == null && 
          m_dataProvider == null && m_instanceProvider == null) {
        return null;
      }
      
      if (eventName.equals("dataSet") && m_dataListeners.size() == 0) {    
        // downstream has asked for the structure of something that we
        // are not producing at the moment
        return null;
      }
      
      if (eventName.equals("trainingSet") && m_trainingListeners.size() == 0) {    
        // downstream has asked for the structure of something that we
        // are not producing at the moment
        return null;
      }
      
      if (eventName.equals("testSet") && m_testListeners.size() == 0) {    
        // downstream has asked for the structure of something that we
        // are not producing at the moment
        return null;
      }
      
      if (eventName.equals("instance") && m_instanceListeners.size() == 0) {    
        // downstream has asked for the structure of something that we
        // are not producing at the moment
        return null;
      }
      
      if (m_connectedFormat == null) {
        m_connectedFormat = getUpstreamStructure();
      }
      
      if (m_connectedFormat != null) {
        assignClass(m_connectedFormat);
      }
      
      return m_connectedFormat;
    }
  
    /**
     * Returns the structure of the incoming instances (if any)
     *
     * @return an <code>Instances</code> value
     */
    public Instances getConnectedFormat() {
      // loaders will push instances format to us
      // when the user makes configuration changes
      // to the loader in the gui. However, if a fully
      // configured flow is loaded then we won't get
      // this information pushed to us until the
      // flow is run. In this case we want to pull
      // it (if possible) from upstream steps so
      // that our customizer can provide the nice
      // UI with the drop down box of class names.    
      if (m_connectedFormat == null) {
        // try and pull the incoming structure
        // from the upstream step (if possible)
        m_connectedFormat = getUpstreamStructure();
      }
      
      return m_connectedFormat;
    }
  
    public void setClassColumn(String col) {
      m_classColumn = col;
      if (m_connectedFormat != null) {
        assignClass(m_connectedFormat);
      }
    }
  
    public String getClassColumn() {
      return m_classColumn;
    }
  
    public void acceptDataSet(DataSetEvent e) {
      Instances dataSet = e.getDataSet();
      assignClass(dataSet);
      notifyDataListeners(e);
      if (e.isStructureOnly()) {
        m_connectedFormat = e.getDataSet();
        // tell any listening customizers (or other
        notifyDataFormatListeners();
      }
    }
  
    public void acceptTrainingSet(TrainingSetEvent e) {
      Instances trainingSet = e.getTrainingSet();
      assignClass(trainingSet);
      notifyTrainingListeners(e);
      
      if (e.isStructureOnly()) {
        m_connectedFormat = e.getTrainingSet();
        // tell any listening customizers (or other
        notifyDataFormatListeners();
      }
    }
  
    public void acceptTestSet(TestSetEvent e) {
      Instances testSet = e.getTestSet();
      assignClass(testSet);
      notifyTestListeners(e);
      if (e.isStructureOnly()) {
        m_connectedFormat = e.getTestSet();
        // tell any listening customizers (or other
        notifyDataFormatListeners();
      }
    }
  
    public void acceptInstance(InstanceEvent e) {
      if (e.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
        //      Instances dataSet = e.getInstance().dataset();
        m_connectedFormat = e.getStructure();
        
        //      System.err.println("Assigning class column...");
        assignClass(m_connectedFormat);
        notifyInstanceListeners(e);
  
        // tell any listening customizers (or other interested parties)
        System.err.println("Notifying customizer...");
        notifyDataFormatListeners();
      } else {
        //      Instances dataSet = e.getInstance().dataset();
        //      assignClass(dataSet);
        notifyInstanceListeners(e);
      }
    }
  
    private void assignClass(Instances dataSet) {
      int classCol = -1;
  
      if (m_classColumn.trim().toLowerCase().compareTo("last") == 0 ||
          m_classColumn.equalsIgnoreCase("/last")) {
        dataSet.setClassIndex(dataSet.numAttributes()-1);
      } else if (m_classColumn.trim().toLowerCase().compareTo("first") == 0 ||
          m_classColumn.equalsIgnoreCase("/first")) {
        dataSet.setClassIndex(0);
      } else {
        // try to look up the class attribute as a string
        Attribute classAtt = dataSet.attribute(m_classColumn.trim());
        if (classAtt != null) {
          dataSet.setClass(classAtt);
        } else {
          // parse it as a number
          classCol = Integer.parseInt(m_classColumn.trim()) - 1;
          if (/*classCol < 0 ||*/ classCol > dataSet.numAttributes()-1) {
            if (m_logger != null) {
              m_logger.logMessage("Class column outside range of data "
                  +"(ClassAssigner)");
            }
          } else {
            dataSet.setClassIndex(classCol);
          }
        }
      }
    }
  
    protected void notifyTestListeners(TestSetEvent tse) {
      Vector l;
      synchronized (this) {
        l = (Vector)m_testListeners.clone();
      }
      if (l.size() > 0) {
        for(int i = 0; i < l.size(); i++) {
  	System.err.println("Notifying test listeners "
  			   +"(ClassAssigner)");
  	((TestSetListener)l.elementAt(i)).acceptTestSet(tse);
        }
      }
    }
  
    protected void notifyTrainingListeners(TrainingSetEvent tse) {
      Vector l;
      synchronized (this) {
        l = (Vector)m_trainingListeners.clone();
      }
      if (l.size() > 0) {
        for(int i = 0; i < l.size(); i++) {
  	System.err.println("Notifying training listeners "
  			   +"(ClassAssigner)");
  	((TrainingSetListener)l.elementAt(i)).acceptTrainingSet(tse);
        }
      }
    }
  
    protected void notifyDataListeners(DataSetEvent tse) {
      Vector l;
      synchronized (this) {
        l = (Vector)m_dataListeners.clone();
      }
      if (l.size() > 0) {
        for(int i = 0; i < l.size(); i++) {
  	System.err.println("Notifying data listeners "
  			   +"(ClassAssigner)");
  	((DataSourceListener)l.elementAt(i)).acceptDataSet(tse);
        }
      }
    }
  
    protected void notifyInstanceListeners(InstanceEvent tse) {
      Vector l;
      synchronized (this) {
        l = (Vector)m_instanceListeners.clone();
      }
      if (l.size() > 0) {
        for(int i = 0; i < l.size(); i++) {
  	//	System.err.println("Notifying instance listeners "
  	//			   +"(ClassAssigner)");
  	
  	((InstanceListener)l.elementAt(i)).acceptInstance(tse);
        }
      }
    }
  
    protected void notifyDataFormatListeners() {
      Vector l;
      synchronized (this) {
        l = (Vector)m_dataFormatListeners.clone();
      }
      if (l.size() > 0) {
        DataSetEvent dse = new DataSetEvent(this, m_connectedFormat);
        for(int i = 0; i < l.size(); i++) {
  	//	System.err.println("Notifying instance listeners "
  	//			   +"(ClassAssigner)");
  	((DataFormatListener)l.elementAt(i)).newDataFormat(dse);
        }
      }
    }
  
    public synchronized void addInstanceListener(InstanceListener tsl) {
      m_instanceListeners.addElement(tsl);
      if (m_connectedFormat != null) {
        InstanceEvent e = new InstanceEvent(this, m_connectedFormat);
        tsl.acceptInstance(e);
      }
    }
  
    public synchronized void removeInstanceListener(InstanceListener tsl) {
      m_instanceListeners.removeElement(tsl);
    }
  
    public synchronized void addDataSourceListener(DataSourceListener tsl) {
      m_dataListeners.addElement(tsl);
      // pass on any format that we might know about
      if (m_connectedFormat != null) {
        DataSetEvent e = new DataSetEvent(this, m_connectedFormat);
        tsl.acceptDataSet(e);
      }
    }
  
    public synchronized void removeDataSourceListener(DataSourceListener tsl) {
      m_dataListeners.removeElement(tsl);
    }
  
    public synchronized void addTrainingSetListener(TrainingSetListener tsl) {
      m_trainingListeners.addElement(tsl);
      // pass on any format that we might know about
      if (m_connectedFormat != null) {
        TrainingSetEvent e = new TrainingSetEvent(this, m_connectedFormat);
        tsl.acceptTrainingSet(e);
      }
    }
  
    public synchronized void removeTrainingSetListener(TrainingSetListener tsl) {
      m_trainingListeners.removeElement(tsl);
    }
  
    public synchronized void addTestSetListener(TestSetListener tsl) {
      m_testListeners.addElement(tsl);
      // pass on any format that we might know about
      if (m_connectedFormat != null) {
        TestSetEvent e = new TestSetEvent(this, m_connectedFormat);
        tsl.acceptTestSet(e);
      }
    }
  
    public synchronized void removeTestSetListener(TestSetListener tsl) {
      m_testListeners.removeElement(tsl);
    }
  
    public synchronized void addDataFormatListener(DataFormatListener dfl) {
      m_dataFormatListeners.addElement(dfl);
    }
  
    public synchronized void removeDataFormatListener(DataFormatListener dfl) {
      m_dataFormatListeners.removeElement(dfl);
    }
  
    public void setVisual(BeanVisual newVisual) {
      m_visual = newVisual;
    }
  
    public BeanVisual getVisual() {
      return m_visual;
    }
    
    public void useDefaultVisual() {
      m_visual.loadIcons(BeanVisual.ICON_PATH+"ClassAssigner.gif",
  		       BeanVisual.ICON_PATH+"ClassAssigner_animated.gif");
    }
  
    /**
     * Returns true if, at this time, 
     * the object will accept a connection according to the supplied
     * event name
     *
     * @param eventName the event
     * @return true if the object will accept a connection
     */
    public boolean connectionAllowed(String eventName) {
      if (eventName.compareTo("trainingSet") == 0 && 
  	(m_trainingProvider != null || m_dataProvider != null ||
  	 m_instanceProvider != null)) { 
        return false;
      }
      
      if (eventName.compareTo("testSet") == 0 && 
  	m_testProvider != null) { 
        return false;
      }
  
       if (eventName.compareTo("instance") == 0 &&
  	m_instanceProvider != null || m_trainingProvider != null ||
  	 m_dataProvider != null) {
         return false;
       } 
      return true;
    }
  
    /**
     * Returns true if, at this time, 
     * the object will accept a connection according to the supplied
     * EventSetDescriptor
     *
     * @param esd the EventSetDescriptor
     * @return true if the object will accept a connection
     */
    public boolean connectionAllowed(EventSetDescriptor esd) {
      return connectionAllowed(esd.getName());
    }
  
    /**
     * Notify this object that it has been registered as a listener with
     * a source with respect to the supplied event name
     *
     * @param eventName the event
     * @param source the source with which this object has been registered as
     * a listener
     */
    public synchronized void connectionNotification(String eventName,
  						  Object source) {
      if (connectionAllowed(eventName)) {
        if (eventName.compareTo("trainingSet") == 0) {
  	m_trainingProvider = source;
        } else if (eventName.compareTo("testSet") == 0) {
  	m_testProvider = source;
        } else if (eventName.compareTo("dataSet") == 0) {
  	m_dataProvider = source;
        } else if (eventName.compareTo("instance") == 0) {
  	m_instanceProvider = source;
        }
        m_connectedFormat = null;
      }
    }
  
    /**
     * Notify this object that it has been deregistered as a listener with
     * a source with respect to the supplied event name
     *
     * @param eventName the event
     * @param source the source with which this object has been registered as
     * a listener
     */
    public synchronized void disconnectionNotification(String eventName,
  						     Object source) {
  
      if (eventName.compareTo("trainingSet") == 0) {
        if (m_trainingProvider == source) {
  	m_trainingProvider = null;
        }
      }
      if (eventName.compareTo("testSet") == 0) {
        if (m_testProvider == source) {
  	m_testProvider = null;
        }
      }
      if (eventName.compareTo("dataSet") == 0) {
        if (m_dataProvider == source) {
  	m_dataProvider = null;
        }
      }
  
      if (eventName.compareTo("instance") == 0) {
        if (m_instanceProvider == source) {
  	m_instanceProvider = null;
        }
      }
      m_connectedFormat = null;
    }
    
    public void setLog(weka.gui.Logger logger) {
      m_logger = logger;
    }
  
    public void stop() {
      // Pass on to upstream beans
      if (m_trainingProvider != null && m_trainingProvider instanceof BeanCommon) {
        ((BeanCommon)m_trainingProvider).stop();
      }
      
      if (m_testProvider != null && m_testProvider instanceof BeanCommon) {
        ((BeanCommon)m_testProvider).stop();
      }
      
      if (m_dataProvider != null && m_dataProvider instanceof BeanCommon) {
        ((BeanCommon)m_dataProvider).stop();
      }
      
      if (m_instanceProvider != null && m_instanceProvider instanceof BeanCommon) {
        ((BeanCommon)m_instanceProvider).stop();
      }
    }
    
    /**
     * 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 false;
    }
    
    /**
     * Returns true, if at the current time, the named event could
     * be generated. Assumes that the supplied event name is
     * an event that could be generated by this bean
     *
     * @param eventName the name of the event in question
     * @return true if the named event could be generated at this point in
     * time
     */
    public boolean eventGeneratable(String eventName) {
      if (eventName.compareTo("trainingSet") == 0) { 
        if (m_trainingProvider == null) {
  	return false;
        } else {
  	if (m_trainingProvider instanceof EventConstraints) {
  	  if (!((EventConstraints)m_trainingProvider).
  	      eventGeneratable("trainingSet")) {
  	    return false;
  	  }
  	}
        }
      }
  
      if (eventName.compareTo("dataSet") == 0) { 
        if (m_dataProvider == null) {
  	if (m_instanceProvider == null) {
  	  m_connectedFormat = null;
  	  notifyDataFormatListeners();
  	}
  	return false;
        } else {
  	if (m_dataProvider instanceof EventConstraints) {
  	  if (!((EventConstraints)m_dataProvider).
  	      eventGeneratable("dataSet")) {
  	    m_connectedFormat = null;
  	    notifyDataFormatListeners();
  	    return false;
  	  }
  	}
        }
      }
  
      if (eventName.compareTo("instance") == 0) { 
        if (m_instanceProvider == null) {
  	if (m_dataProvider == null) {
  	  m_connectedFormat = null;
  	  notifyDataFormatListeners();
  	}
  	return false;
        } else {
  	if (m_instanceProvider instanceof EventConstraints) {
  	  if (!((EventConstraints)m_instanceProvider).
  	      eventGeneratable("instance")) {
  	    m_connectedFormat = null;
  	    notifyDataFormatListeners();
  	    return false;
  	  }
  	}
        }
      }
  
      if (eventName.compareTo("testSet") == 0) {
        if (m_testProvider == null) {
  	return false;
        } else {
  	if (m_testProvider instanceof EventConstraints) {
  	  if (!((EventConstraints)m_testProvider).
  	      eventGeneratable("testSet")) {
  	    return false;
  	  }
  	}
        }
      }
      return true;
    }
  }
  