All Downloads are FREE. Search and download functionalities are using the official Maven repository.

weka.gui.beans.MetaBean Maven / Gradle / Ivy

Go to download

The Waikato Environment for Knowledge Analysis (WEKA), a machine learning workbench. This is the stable version. Apart from bugfixes, this version does not receive any other updates.

There is a newer version: 3.8.6
Show newest version
/*
 *   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 3 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, see .
 */

/*
 *    MetaBean.java
 *    Copyright (C) 2005-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.gui.beans;

import java.awt.BorderLayout;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.BeanInfo;
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.Set;
import java.util.TreeMap;
import java.util.Vector;

import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JWindow;

import weka.gui.Logger;

/**
 * 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: 10221 $
 */
public class MetaBean extends JPanel implements BeanCommon, Visible,
  EventConstraints, Serializable, UserRequestAcceptor, Startable {

  /** 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
   */
  @Override
  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)
   */
  @Override
  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 = m_originalCoords.elementAt(i);
      JComponent c = (JComponent) temp.getBean();
      c.getPreferredSize();
      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 EventSetDescriptor value
   * @return a boolean 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
   */
  @Override
  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
   */
  @Override
  public boolean connectionAllowed(EventSetDescriptor esd) {
    Vector targets = getSuitableTargets(esd);
    for (int i = 0; i < targets.size(); i++) {
      BeanInstance input = 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;
  }

  @Override
  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
   */
  @Override
  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
   */
  @Override
  public synchronized void disconnectionNotification(String eventName,
    Object source) {

  }

  /**
   * Stop all encapsulated beans
   */
  @Override
  public void stop() {
    for (int i = 0; i < m_subFlow.size(); i++) {
      Object temp = m_subFlow.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.
   */
  @Override
  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 BeanVisual value
   */
  @Override
  public void setVisual(BeanVisual newVisual) {
    m_visual = newVisual;
  }

  /**
   * Gets the visual appearance of this wrapper bean
   */
  @Override
  public BeanVisual getVisual() {
    return m_visual;
  }

  /**
   * Use the default visual appearance for this bean
   */
  @Override
  public void useDefaultVisual() {
    m_visual.loadIcons(BeanVisual.ICON_PATH + "DiamondPlain.gif",
      BeanVisual.ICON_PATH + "DiamondPlain.gif");
  }

  @Override
  public String getStartMessage() {
    String message = "Start loading";
    for (int i = 0; i < m_subFlow.size(); i++) {
      BeanInstance temp = (BeanInstance) m_subFlow.elementAt(i);
      if (temp.getBean() instanceof Startable) {
        String s = ((Startable) temp.getBean()).getStartMessage();
        if (s.startsWith("$")) {
          message = "$" + message;
          break;
        }
      }
    }

    return message;
  }

  @Override
  public void start() {
    TreeMap startables = new TreeMap();
    for (int i = 0; i < m_subFlow.size(); i++) {
      BeanInstance temp = (BeanInstance) m_subFlow.elementAt(i);
      if (temp.getBean() instanceof Startable) {
        Startable s = (Startable) temp.getBean();
        String beanName = s.getClass().getName();
        String customName = beanName;

        boolean ok = false;
        Integer position = null;
        boolean launch = true;
        // String beanName = s.getClass().getName();
        if (s instanceof BeanCommon) {
          customName = ((BeanCommon) s).getCustomName();
          beanName = customName;
          // see if we have a parseable integer at the start of the name
          if (customName.indexOf(':') > 0) {
            if (customName.substring(0, customName.indexOf(':'))
              .startsWith("!")) {
              launch = false;
            } else {
              String startPos = customName
                .substring(0, customName.indexOf(':'));

              try {
                position = new Integer(startPos);
                ok = true;
              } catch (NumberFormatException n) {
              }
            }
          }
        }

        if (!ok && launch) {
          if (startables.size() == 0) {
            position = new Integer(0);
          } else {
            int newPos = startables.lastKey().intValue();
            newPos++;
            position = new Integer(newPos);
          }
        }

        if (s.getStartMessage().charAt(0) != '$') {
          if (launch) {
            if (m_log != null) {
              m_log.logMessage(statusMessagePrefix() + "adding start point "
                + beanName + " to the execution list (position " + position
                + ")");
            }
            startables.put(position, s);
          }
        }
      }
    }

    if (startables.size() > 0) {
      if (m_log != null) {
        m_log.logMessage(statusMessagePrefix() + "Starting "
          + startables.size() + " sub-flow start points sequentially.");
      }
      Set s = startables.keySet();
      for (Integer i : s) {
        try {
          Startable startPoint = startables.get(i);
          String bN = startPoint.getClass().getName();
          if (startPoint instanceof BeanCommon) {
            bN = ((BeanCommon) startPoint).getCustomName();
          }
          if (m_log != null) {
            m_log.statusMessage(statusMessagePrefix()
              + "Starting sub-flow start point: " + bN);
            m_log.logMessage(statusMessagePrefix()
              + "Starting sub-flow start point: " + bN);
          }
          startPoint.start();
          Thread.sleep(500);
          while (isBusy()) {
            Thread.sleep(2000);
          }
        } catch (Exception ex) {
          if (m_log != null) {
            m_log.logMessage(statusMessagePrefix()
              + "A problem occurred when launching start points in sub-flow: "
              + ex.getMessage());
          }
          stop();
          if (m_log != null) {
            m_log.statusMessage(statusMessagePrefix()
              + "ERROR (see log for details)");
          }
        }
      }
      if (m_log != null) {
        m_log.statusMessage(statusMessagePrefix() + "Finished.");
      }
    }
  }

  /**
   * Return an enumeration of requests that can be made by the user
   * 
   * @return an Enumeration value
   */
  @Override
  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 = 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() {
        @Override
        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() {
          @Override
          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
   */
  @Override
  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 Logger value
   */
  @Override
  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;
  }

  private String statusMessagePrefix() {
    return getCustomName() + "$" + MetaBean.this.hashCode() + "|";
  }
}