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.
   */
  
  /*
   *    MetaBean.java
   *    Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
   *
   */
  
  package weka.gui.beans;
  
  import weka.gui.Logger;
  
  import java.awt.BorderLayout;
  import java.awt.Dimension;
  import java.awt.Point;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.beans.EventSetDescriptor;
  import java.beans.IntrospectionException;
  import java.beans.Introspector;
  import java.beans.PropertyChangeListener;
  import java.io.Serializable;
  import java.util.Enumeration;
  import java.util.Vector;
  
  import javax.swing.ImageIcon;
  import javax.swing.JComponent;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
  import javax.swing.JWindow;
  
  /**
   * A meta bean that encapsulates several other regular beans, useful for 
   * grouping large KnowledgeFlows.
   *
   *
   * @author Mark Hall (mhall at cs dot waikato dot ac dot nz)
   * @version $Revision: 7155 $
   */
  public class MetaBean
    extends JPanel 
    implements BeanCommon, Visible, EventConstraints,
               Serializable, UserRequestAcceptor {
  
    /** for serialization */
    private static final long serialVersionUID = -6582768902038027077L;
  
    protected BeanVisual m_visual = 
      new BeanVisual("Group",
  		   BeanVisual.ICON_PATH+"DiamondPlain.gif",
  		   BeanVisual.ICON_PATH+"DiamondPlain.gif");
  
    private transient Logger m_log = null;
    private transient JWindow m_previewWindow = null;
    private transient javax.swing.Timer m_previewTimer = null;
  
    protected Vector m_subFlow = new Vector();
    protected Vector m_inputs = new Vector();
    protected Vector m_outputs = new Vector();
  
    // the internal connections for the grouping
    protected Vector m_associatedConnections = new Vector();
    
    // Holds a preview image of the encapsulated sub-flow
    protected ImageIcon m_subFlowPreview = null;
    
    // offset from where originally grouped if the meta bean 
    // dropped onto the canvas from the user toolbar
    protected int m_xCreate = 0;
    protected int m_yCreate = 0;
    protected int m_xDrop = 0;
    protected int m_yDrop = 0;
  
    public MetaBean() {
      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();
    }
  
    public void setAssociatedConnections(Vector ac) {
      m_associatedConnections = ac;
    }
  
    public Vector getAssociatedConnections() {
      return m_associatedConnections;
    }
  
    public void setSubFlow(Vector sub) {
      m_subFlow = sub;
    }
  
    public Vector getSubFlow() {
      return m_subFlow;
    }
  
    public void setInputs(Vector inputs) {
      m_inputs = inputs;
    }
  
    public Vector getInputs() {
      return m_inputs;
    }
  
    public void setOutputs(Vector outputs) {
      m_outputs = outputs;
    }
  
    public Vector getOutputs() {
      return m_outputs;
    }
  
    private Vector getBeans(Vector beans, int type) {
      Vector comps = new Vector();
      for (int i = 0; i < beans.size(); i++) {
        BeanInstance temp = (BeanInstance)beans.elementAt(i);
        // need to check for sub MetaBean!
        if (temp.getBean() instanceof MetaBean) {
          switch (type) {
          case 0 : 
            comps.addAll(((MetaBean)temp.getBean()).getBeansInSubFlow());
            break;
          case 1 : 
            comps.addAll(((MetaBean)temp.getBean()).getBeansInInputs());
            break;
          case 2:
            comps.addAll(((MetaBean)temp.getBean()).getBeansInOutputs());
            break;
          }
        } else {
          comps.add(temp);
        }
      }
      return comps;
    }
    
    private boolean beanSetContains(Vector set, BeanInstance toCheck) {
      boolean ok = false;
      
      for (int i = 0; i < set.size(); i++) {
        BeanInstance temp = (BeanInstance)set.elementAt(i);
        if (toCheck == temp) {
          ok = true;
          break;
        }
      }
      return ok;
    }
    
    public boolean subFlowContains(BeanInstance toCheck) {
      return beanSetContains(m_subFlow, toCheck);
    }
    
    public boolean inputsContains(BeanInstance toCheck) {
      return beanSetContains(m_inputs, toCheck);
    }
    
    public boolean outputsContains(BeanInstance toCheck) {
      return beanSetContains(m_outputs, toCheck);
    }
  
    /**
     * Return all the beans in the sub flow
     *
     * @return a Vector of all the beans in the sub flow
     */
    public Vector getBeansInSubFlow() {
      return getBeans(m_subFlow, 0);
    }
  
    /**
     * Return all the beans in the inputs
     *
     * @return a Vector of all the beans in the inputs
     */
    public Vector getBeansInInputs() {
      return getBeans(m_inputs, 1);
    }
  
    /**
     * Return all the beans in the outputs
     *
     * @return a Vector of all the beans in the outputs
     */
    public Vector getBeansInOutputs() {
      return getBeans(m_outputs, 2);
    }
  
    private Vector getBeanInfos(Vector beans, int type) {
      Vector infos = new Vector();
      for (int i = 0; i < beans.size(); i++) {
        BeanInstance temp = (BeanInstance)beans.elementAt(i);
        if (temp.getBean() instanceof MetaBean) {
          switch (type) {
          case 0: 
            infos.addAll(((MetaBean)temp.getBean()).getBeanInfoSubFlow());
            break;
          case 1: 
            infos.addAll(((MetaBean)temp.getBean()).getBeanInfoInputs());
            break;
          case 2:
            infos.addAll(((MetaBean)temp.getBean()).getBeanInfoOutputs());
          }
        } else {
          try {
            infos.add(Introspector.getBeanInfo(temp.getBean().getClass()));
          } catch (IntrospectionException ex) {
            ex.printStackTrace();
          }
        }
      }
      return infos;
    }
  
    public Vector getBeanInfoSubFlow() {
      return getBeanInfos(m_subFlow, 0);
    }
  
    public Vector getBeanInfoInputs() {
      return getBeanInfos(m_inputs, 1);
    }
  
    public Vector getBeanInfoOutputs() {
      return getBeanInfos(m_outputs, 2);
    }
  
    // stores the original position of the beans 
    // when this group is created. Used
    // to restore their locations if the group is ungrouped.
    private Vector m_originalCoords;
  
    /**
     * returns the vector containing the original coordinates (instances of class
     * Point) for the inputs
     * @return the containing the coord Points of the original inputs
     */
    public Vector getOriginalCoords() {
      return m_originalCoords;
    }
    
    /**
     * sets the vector containing the original coordinates (instances of class
     * Point) for the inputs
     * @param value the vector containing the points of the coords of the original inputs
     */
    public void setOriginalCoords(Vector value) {
      m_originalCoords = value;
    }
  
    /**
     * Move coords of all inputs and outputs of this meta bean
     * to the coords of the supplied BeanInstance. Typically
     * the supplied BeanInstance is the BeanInstance that encapsulates
     * this meta bean; the result in this case is that all inputs
     * and outputs are shifted so that their coords coincide with
     * the meta bean and all connections to them appear (visually) to
     * go to/from the meta bean.
     *
     * @param toShiftTo the BeanInstance whos coordinates will
     * be used.
     * @param save true if coordinates are to be saved.
     */
    public void shiftBeans(BeanInstance toShiftTo, 
                           boolean save) {
      if (save) {
        m_originalCoords = new Vector();
      }
      int targetX = toShiftTo.getX();
      int targetY = toShiftTo.getY();
  
      for (int i = 0; i < m_subFlow.size(); i++) {
        BeanInstance temp = (BeanInstance)m_subFlow.elementAt(i);
        if (save) {
          // save offsets from this point
          Point p = new Point(temp.getX() - targetX, temp.getY() - targetY);
          m_originalCoords.add(p);
        }
        temp.setX(targetX); temp.setY(targetY);
      }
    }
  
    public void restoreBeans(int x, int y) {
      for (int i = 0; i < m_subFlow.size(); i++) {
        BeanInstance temp = (BeanInstance)m_subFlow.elementAt(i);
        Point p = (Point)m_originalCoords.elementAt(i);
        JComponent c = (JComponent)temp.getBean();
        Dimension d = c.getPreferredSize();
        int dx = (int)(d.getWidth() / 2);
        int dy = (int)(d.getHeight() / 2);
        temp.setX(x + (int)p.getX());// + (m_xDrop - m_xCreate));
        temp.setY(y + (int)p.getY());// + (m_yDrop - m_yCreate));
      }
    }
  
    /**
     * Returns true, if at the current time, the event described by the
     * supplied event descriptor could be generated.
     *
     * @param esd an <code>EventSetDescriptor</code> value
     * @return a <code>boolean</code> value
     */
    public boolean eventGeneratable(EventSetDescriptor esd) {
      String eventName = esd.getName();
      return eventGeneratable(eventName);
    }
  
    /**
     * 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) {
      for (int i = 0; i < m_subFlow.size(); i++) {
        BeanInstance output = (BeanInstance)m_subFlow.elementAt(i);
        if (output.getBean() instanceof EventConstraints) {
          if (((EventConstraints)output.getBean()).eventGeneratable(eventName)) {
            return true;
          }
        }
      }
      return false;
    }
  
    /**
     * Returns true if, at this time, 
     * the object will accept a connection with respect to the
     * supplied EventSetDescriptor
     *
     * @param esd the EventSetDescriptor
     * @return true if the object will accept a connection
     */
    public boolean connectionAllowed(EventSetDescriptor esd) {
      Vector targets = getSuitableTargets(esd);
      for (int i = 0; i < targets.size(); i++) {
        BeanInstance input = (BeanInstance)targets.elementAt(i);
        if (input.getBean() instanceof BeanCommon) {
          //if (((BeanCommon)input.getBean()).connectionAllowed(esd.getName())) {
          if (((BeanCommon)input.getBean()).connectionAllowed(esd)) {
            return true;
          }
        } else {
          return true;
        }
      }
      return false;
    }
  
    public boolean connectionAllowed(String eventName) {
      return false;
    }
  
    /**
     * Notify this object that it has been registered as a listener with
     * a source with respect to the named event. This is just a dummy
     * method in this class to satisfy the interface. Specific code
     * in BeanConnection takes care of this method for MetaBeans
     *
     * @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) {
    }
    
    /**
     * Notify this object that it has been deregistered as a listener with
     * a source with respect to the supplied event name. This is just a dummy
     * method in this class to satisfy the interface. Specific code
     * in BeanConnection takes care of this method for MetaBeans
     *
     * @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) {
  
    }
  
    /**
     * Stop all encapsulated beans
     */
    public void stop() {
      for (int i = 0; i < m_inputs.size(); i++) {
        Object temp = m_inputs.elementAt(i);
        if (temp instanceof BeanCommon) {
          ((BeanCommon)temp).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() {
      boolean result = false;
      for (int i = 0; i < m_subFlow.size(); i++) {
        Object temp = m_subFlow.elementAt(i);
        if (temp instanceof BeanCommon) {
          if (((BeanCommon)temp).isBusy()) {
            result = true;
            break;
          }
        }
      }
      return result;
    }
  
    /**
     * Sets the visual appearance of this wrapper bean
     *
     * @param newVisual a <code>BeanVisual</code> value
     */
    public void setVisual(BeanVisual newVisual) {
      m_visual = newVisual;
    }
  
    /**
     * Gets the visual appearance of this wrapper bean
     */
    public BeanVisual getVisual() {
      return m_visual;
    }
  
    /**
     * Use the default visual appearance for this bean
     */
    public void useDefaultVisual() {
      m_visual.loadIcons(BeanVisual.ICON_PATH+"DiamondPlain.gif",
  		       BeanVisual.ICON_PATH+"DiamondPlain.gif");
    }
  
    /**
     * Return an enumeration of requests that can be made by the user
     *
     * @return an <code>Enumeration</code> value
     */
    public Enumeration enumerateRequests() {
      Vector newVector = new Vector();
      if (m_subFlowPreview != null) {
        String text = "Show preview";
        if (m_previewWindow != null) {
  	text = "$"+text;
        }
        newVector.addElement(text);
      }
      for (int i = 0; i < m_subFlow.size(); i++) {
        BeanInstance temp = (BeanInstance)m_subFlow.elementAt(i);
        if (temp.getBean() instanceof UserRequestAcceptor) {
          String prefix = "";
          if ((temp.getBean() instanceof BeanCommon)) {
            prefix = ((BeanCommon)temp.getBean()).getCustomName();
          } else {
            prefix = temp.getBean().getClass().getName();
            prefix = prefix.substring(prefix.lastIndexOf('.')+1, prefix.length());
          }
          prefix = ""+(i+1)+": ("+prefix+")";
          Enumeration en = ((UserRequestAcceptor)temp.getBean()).enumerateRequests();
          while (en.hasMoreElements()) {
            String req = (String)en.nextElement();
            if (req.charAt(0) == '$') {
              prefix = '$'+prefix;
              req = req.substring(1, req.length());
            }
            
            if (req.charAt(0) == '?') {
              prefix = '?' + prefix;
              req = req.substring(1, req.length());
            }
            newVector.add(prefix+" "+req);
          }          
        } else if (temp.getBean() instanceof Startable) {
          String prefix = "";
          if ((temp.getBean() instanceof BeanCommon)) {
            prefix = ((BeanCommon)temp.getBean()).getCustomName();
          } else {
            prefix = temp.getBean().getClass().getName();
            prefix = prefix.substring(prefix.lastIndexOf('.')+1, prefix.length());
          }
          prefix = ""+(i+1)+": ("+prefix+")";
          String startMessage = ((Startable)temp.getBean()).getStartMessage();
          if (startMessage.charAt(0) == '$') {
            prefix = '$'+prefix;
            startMessage = startMessage.substring(1, startMessage.length());
          }
          newVector.add(prefix + " " + startMessage);
        }
      }
      
      return newVector.elements();
    }
    
    public void setSubFlowPreview(ImageIcon sfp) {
      m_subFlowPreview = sfp;
    }
    
    private void showPreview() {
      if (m_previewWindow == null) {
        
        JLabel jl = new JLabel(m_subFlowPreview);
        //Dimension d = jl.getPreferredSize();
        jl.setLocation(0,0);
        m_previewWindow = new JWindow();
        //popup.getContentPane().setLayout(null);
        m_previewWindow.getContentPane().add(jl);
        m_previewWindow.validate();
        m_previewWindow.setSize(m_subFlowPreview.getIconWidth(), m_subFlowPreview.getIconHeight());
        
        m_previewWindow.addMouseListener(new MouseAdapter() {
  	  public void mouseClicked(MouseEvent e) {
  	    m_previewWindow.dispose();
  	    m_previewWindow = null;
  	  }
  	});
        
        m_previewWindow.setLocation(
  	  getParent().getLocationOnScreen().x + getX() + getWidth() / 2 - 
  	  m_subFlowPreview.getIconWidth() / 2, 
  	  getParent().getLocationOnScreen().y + getY() + getHeight() / 2 - 
  	  m_subFlowPreview.getIconHeight() / 2);
        //popup.pack();
        m_previewWindow.setVisible(true);
        m_previewTimer = 
  	new javax.swing.Timer(8000, new java.awt.event.ActionListener() {
  	  public void actionPerformed(java.awt.event.ActionEvent e) {
  	    if (m_previewWindow != null) {
  	      m_previewWindow.dispose();
  	      m_previewWindow = null;
  	      m_previewTimer = null;
  	    }
  	  }
  	});
        m_previewTimer.setRepeats(false);
        m_previewTimer.start();
      }
    }
  
    /**
     * Perform a particular request
     *
     * @param request the request to perform
     * @exception IllegalArgumentException if an error occurs
     */
    public void performRequest(String request) {
      if (request.compareTo("Show preview") == 0) {
        showPreview();
        return;
      }
      // first grab the index if any
      if (request.indexOf(":") < 0) {
        return;
      }
      String tempI = request.substring(0, request.indexOf(':'));
      int index = Integer.parseInt(tempI);
      index--;
      String req = request.substring(request.indexOf(')')+1, 
                                     request.length()).trim();
      
      Object target = (((BeanInstance)m_subFlow.elementAt(index)).getBean());
      if (target instanceof Startable && req.equals(((Startable)target).getStartMessage())) {
        try {
          ((Startable)target).start();
        } catch (Exception ex) {
          if (m_log != null) {
            String compName = (target instanceof BeanCommon) ? ((BeanCommon)target).getCustomName() : "";
            m_log.logMessage("Problem starting subcomponent " + compName);
          }
        }
      } else {    
        ((UserRequestAcceptor)target).performRequest(req);
      }                                   
    }
  
    /**
     * Set a logger
     *
     * @param logger a <code>Logger</code> value
     */
    public void setLog(Logger logger) {
      m_log = logger;
    }
  
    public void removePropertyChangeListenersSubFlow(PropertyChangeListener pcl) {
      for (int i = 0; i < m_subFlow.size(); i++) {
        BeanInstance temp = (BeanInstance)m_subFlow.elementAt(i);
        if (temp.getBean() instanceof Visible) {
          ((Visible)(temp.getBean())).getVisual().
            removePropertyChangeListener(pcl);
        }
        if (temp.getBean() instanceof MetaBean) {
          ((MetaBean)temp.getBean()).removePropertyChangeListenersSubFlow(pcl);
        }
      }
    }
  
    public void addPropertyChangeListenersSubFlow(PropertyChangeListener pcl) {
      for (int i = 0; i < m_subFlow.size(); i++) {
        BeanInstance temp = (BeanInstance)m_subFlow.elementAt(i);
        if (temp.getBean() instanceof Visible) {
          ((Visible)(temp.getBean())).getVisual().
            addPropertyChangeListener(pcl);
        }
        if (temp.getBean() instanceof MetaBean) {
          ((MetaBean)temp.getBean()).addPropertyChangeListenersSubFlow(pcl);
        }
      }
    }
  
    /**
     * Checks to see if any of the inputs to this group implements
     * the supplied listener class
     *
     * @param listenerClass the listener to check for
     */
    public boolean canAcceptConnection(Class listenerClass) {
      for (int i = 0; i < m_inputs.size(); i++) {
        BeanInstance input = (BeanInstance)m_inputs.elementAt(i);
        if (listenerClass.isInstance(input.getBean())) {
          return true;
        }
      }
      return false;
    }
  
    /**
     * Return a list of input beans capable of receiving the 
     * supplied event
     *
     * @param esd the event in question
     * @return a vector of beans capable of handling the event
     */
    public Vector getSuitableTargets(EventSetDescriptor esd) {
      Class listenerClass = esd.getListenerType(); // class of the listener
      Vector targets = new Vector();
      for (int i = 0; i < m_inputs.size(); i++) {
        BeanInstance input = (BeanInstance)m_inputs.elementAt(i);
        if (listenerClass.isInstance(input.getBean())) {
          targets.add(input);
        }
      }
      return targets;
    }
  }
  