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

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

/*
 *   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 .
 */

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

package weka.gui.beans;

import java.awt.BorderLayout;
import java.beans.EventSetDescriptor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Hashtable;
import java.util.Vector;

import javax.swing.JPanel;

import weka.associations.Apriori;
import weka.associations.AssociationRules;
import weka.associations.AssociationRulesProducer;
import weka.core.Attribute;
import weka.core.Environment;
import weka.core.EnvironmentHandler;
import weka.core.Instances;
import weka.core.OptionHandler;
import weka.core.Utils;
import weka.gui.Logger;

/**
 * Bean that wraps around weka.associations. If used in a non-graphical
 * environment, options for the wrapped associator can be provided by setting an
 * environment variable: weka.gui.beans.associator.schemeOptions. The value of
 * this environment variable needs to be a string containing command-line option
 * settings.
 * 
 * @author Mark Hall (mhall at cs dot waikato dot ac dot nz)
 * @version $Revision: 10216 $
 * @since 1.0
 * @see JPanel
 * @see BeanCommon
 * @see Visible
 * @see WekaWrapper
 * @see Serializable
 * @see UserRequestAcceptor
 * @see TrainingSetListener
 * @see DataSourceListener
 */
public class Associator extends JPanel implements BeanCommon, Visible,
  WekaWrapper, EventConstraints, Serializable, UserRequestAcceptor,
  DataSourceListener, TrainingSetListener, ConfigurationProducer,
  StructureProducer, EnvironmentHandler {

  /** for serialization */
  private static final long serialVersionUID = -7843500322130210057L;

  protected BeanVisual m_visual = new BeanVisual("Associator",
    BeanVisual.ICON_PATH + "DefaultAssociator.gif", BeanVisual.ICON_PATH
      + "DefaultAssociator_animated.gif");

  private static int IDLE = 0;
  private static int BUILDING_MODEL = 1;

  private int m_state = IDLE;

  private Thread m_buildThread = null;

  /**
   * Global info for the wrapped associator (if it exists).
   */
  protected String m_globalInfo;

  /**
   * Objects talking to us
   */
  private final Hashtable m_listenees = new Hashtable();

  /**
   * Objects listening for text events
   */
  private final Vector m_textListeners = new Vector();

  /**
   * Objects listening for graph events
   */
  private final Vector m_graphListeners = new Vector();

  /** The objects listening for batchAssociationRules events **/
  private final Vector m_rulesListeners = new Vector();

  private weka.associations.Associator m_Associator = new Apriori();

  private transient Logger m_log = null;

  /** The environment variables */
  private transient Environment m_env = null;

  /**
   * Global info (if it exists) for the wrapped classifier
   * 
   * @return the global info
   */
  public String globalInfo() {
    return m_globalInfo;
  }

  /**
   * Creates a new Associator instance.
   */
  public Associator() {
    setLayout(new BorderLayout());
    add(m_visual, BorderLayout.CENTER);
    setAssociator(m_Associator);
  }

  /**
   * Set environment variables to use.
   * 
   * @param env the environment variables to use
   */
  @Override
  public void setEnvironment(Environment env) {
    m_env = env;
  }

  /**
   * 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();
  }

  /**
   * Set the associator for this wrapper
   * 
   * @param c a weka.associations.Associator value
   */
  public void setAssociator(weka.associations.Associator c) {
    boolean loadImages = true;
    if (c.getClass().getName().compareTo(m_Associator.getClass().getName()) == 0) {
      loadImages = false;
    }
    m_Associator = c;
    String associatorName = c.getClass().toString();
    associatorName = associatorName.substring(
      associatorName.lastIndexOf('.') + 1, associatorName.length());
    if (loadImages) {
      if (!m_visual.loadIcons(BeanVisual.ICON_PATH + associatorName + ".gif",
        BeanVisual.ICON_PATH + associatorName + "_animated.gif")) {
        useDefaultVisual();
      }
    }
    m_visual.setText(associatorName);

    // get global info
    m_globalInfo = KnowledgeFlowApp.getGlobalInfo(m_Associator);
  }

  /**
   * Get the associator currently set for this wrapper
   * 
   * @return a weka.associations.Associator value
   */
  public weka.associations.Associator getAssociator() {
    return m_Associator;
  }

  /**
   * Sets the algorithm (associator) for this bean
   * 
   * @param algorithm an Object value
   * @exception IllegalArgumentException if an error occurs
   */
  @Override
  public void setWrappedAlgorithm(Object algorithm) {

    if (!(algorithm instanceof weka.associations.Associator)) {
      throw new IllegalArgumentException(algorithm.getClass() + " : incorrect "
        + "type of algorithm (Associator)");
    }
    setAssociator((weka.associations.Associator) algorithm);
  }

  /**
   * Returns the wrapped associator
   * 
   * @return an Object value
   */
  @Override
  public Object getWrappedAlgorithm() {
    return getAssociator();
  }

  /**
   * Accept a training set
   * 
   * @param e a TrainingSetEvent value
   */
  @Override
  public void acceptTrainingSet(TrainingSetEvent e) {
    // construct and pass on a DataSetEvent
    Instances trainingSet = e.getTrainingSet();
    DataSetEvent dse = new DataSetEvent(this, trainingSet);
    acceptDataSet(dse);
  }

  @Override
  public void acceptDataSet(final DataSetEvent e) {
    if (e.isStructureOnly()) {
      // no need to build an associator, just absorb and return
      return;
    }

    if (m_buildThread == null) {
      try {
        if (m_state == IDLE) {
          synchronized (this) {
            m_state = BUILDING_MODEL;
          }
          final Instances trainingData = e.getDataSet();
          // final String oldText = m_visual.getText();
          m_buildThread = new Thread() {
            @SuppressWarnings("deprecation")
            @Override
            public void run() {
              try {
                if (trainingData != null) {
                  m_visual.setAnimated();
                  // m_visual.setText("Building model...");
                  if (m_log != null) {
                    m_log.statusMessage(statusMessagePrefix()
                      + "Building model...");
                  }
                  buildAssociations(trainingData);

                  if (m_textListeners.size() > 0) {
                    String modelString = m_Associator.toString();
                    String titleString = m_Associator.getClass().getName();

                    titleString = titleString.substring(
                      titleString.lastIndexOf('.') + 1, titleString.length());
                    modelString = "=== Associator model ===\n\n" + "Scheme:   "
                      + titleString + "\n" + "Relation: "
                      + trainingData.relationName() + "\n\n" + modelString;
                    titleString = "Model: " + titleString;

                    TextEvent nt = new TextEvent(Associator.this, modelString,
                      titleString);
                    notifyTextListeners(nt);
                  }

                  if (m_Associator instanceof weka.core.Drawable
                    && m_graphListeners.size() > 0) {
                    String grphString = ((weka.core.Drawable) m_Associator)
                      .graph();
                    int grphType = ((weka.core.Drawable) m_Associator)
                      .graphType();
                    String grphTitle = m_Associator.getClass().getName();
                    grphTitle = grphTitle.substring(
                      grphTitle.lastIndexOf('.') + 1, grphTitle.length());
                    grphTitle = " (" + e.getDataSet().relationName() + ") "
                      + grphTitle;

                    GraphEvent ge = new GraphEvent(Associator.this, grphString,
                      grphTitle, grphType);
                    notifyGraphListeners(ge);
                  }

                  if ((m_Associator instanceof AssociationRulesProducer)
                    && m_rulesListeners.size() > 0) {
                    AssociationRules rules = ((AssociationRulesProducer) m_Associator)
                      .getAssociationRules();

                    BatchAssociationRulesEvent bre = new BatchAssociationRulesEvent(
                      Associator.this, rules);
                    notifyRulesListeners(bre);
                  }
                }
              } catch (Exception ex) {
                Associator.this.stop();
                if (m_log != null) {
                  m_log.statusMessage(statusMessagePrefix()
                    + "ERROR (See log for details)");
                  m_log.logMessage("[Associator] " + statusMessagePrefix()
                    + " problem training associator. " + ex.getMessage());
                }
                ex.printStackTrace();
              } finally {
                // m_visual.setText(oldText);
                m_visual.setStatic();
                m_state = IDLE;
                if (isInterrupted()) {
                  if (m_log != null) {
                    String titleString = m_Associator.getClass().getName();
                    titleString = titleString.substring(
                      titleString.lastIndexOf('.') + 1, titleString.length());
                    m_log.logMessage("[Associator] " + statusMessagePrefix()
                      + " Build associator interrupted!");
                    m_log.statusMessage(statusMessagePrefix() + "INTERRUPTED");
                  }
                } else {
                  if (m_log != null) {
                    m_log.statusMessage(statusMessagePrefix() + "Finished.");
                  }
                }
                block(false);
              }
            }
          };
          m_buildThread.setPriority(Thread.MIN_PRIORITY);
          m_buildThread.start();
          // make sure the thread is still running before we block
          // if (m_buildThread.isAlive()) {
          block(true);
          // }
          m_buildThread = null;
          m_state = IDLE;
        }
      } catch (Exception ex) {
        ex.printStackTrace();
      }
    }
  }

  private void buildAssociations(Instances data) throws Exception {

    // see if there is an environment variable with
    // options for the associator
    if (m_env != null && m_Associator instanceof OptionHandler) {
      String opts = m_env
        .getVariableValue("weka.gui.beans.associator.schemeOptions");
      if (opts != null && opts.length() > 0) {
        String[] options = Utils.splitOptions(opts);
        if (options.length > 0) {
          try {
            ((OptionHandler) m_Associator).setOptions(options);
          } catch (Exception ex) {
            String warningMessage = "[Associator] WARNING: unable to set options \""
              + opts + "\"for " + m_Associator.getClass().getName();
            if (m_log != null) {
              m_log.logMessage(warningMessage);
            } else {
              System.err.print(warningMessage);
            }
          }
        }
      }
    }

    m_Associator.buildAssociations(data);
  }

  /**
   * 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 + "DefaultAssociator.gif",
      BeanVisual.ICON_PATH + "DefaultAssociator_animated.gif");
  }

  /**
   * Add a batch association rules listener
   * 
   * @param al a BatchAssociationRulesListener
   */
  public synchronized void addBatchAssociationRulesListener(
    BatchAssociationRulesListener al) {
    m_rulesListeners.add(al);
  }

  /**
   * Remove a batch association rules listener
   * 
   * @param al a BatchAssociationRulesListener
   */
  public synchronized void removeBatchAssociationRulesListener(
    BatchAssociationRulesListener al) {
    m_rulesListeners.remove(al);
  }

  /**
   * Add a text listener
   * 
   * @param cl a TextListener value
   */
  public synchronized void addTextListener(TextListener cl) {
    m_textListeners.addElement(cl);
  }

  /**
   * Remove a text listener
   * 
   * @param cl a TextListener value
   */
  public synchronized void removeTextListener(TextListener cl) {
    m_textListeners.remove(cl);
  }

  /**
   * Add a graph listener
   * 
   * @param cl a GraphListener value
   */
  public synchronized void addGraphListener(GraphListener cl) {
    m_graphListeners.addElement(cl);
  }

  /**
   * Remove a graph listener
   * 
   * @param cl a GraphListener value
   */
  public synchronized void removeGraphListener(GraphListener cl) {
    m_graphListeners.remove(cl);
  }

  /**
   * We don't have to keep track of configuration listeners (see the
   * documentation for ConfigurationListener/ConfigurationEvent).
   * 
   * @param cl a ConfigurationListener.
   */
  @Override
  public synchronized void addConfigurationListener(ConfigurationListener cl) {

  }

  /**
   * We don't have to keep track of configuration listeners (see the
   * documentation for ConfigurationListener/ConfigurationEvent).
   * 
   * @param cl a ConfigurationListener.
   */
  @Override
  public synchronized void removeConfigurationListener(ConfigurationListener cl) {

  }

  /**
   * Notify all text listeners of a text event
   * 
   * @param ge a TextEvent value
   */
  @SuppressWarnings("unchecked")
  private void notifyTextListeners(TextEvent ge) {
    Vector l;
    synchronized (this) {
      l = (Vector) m_textListeners.clone();
    }
    if (l.size() > 0) {
      for (int i = 0; i < l.size(); i++) {
        ((TextListener) l.elementAt(i)).acceptText(ge);
      }
    }
  }

  /**
   * Notify all graph listeners of a graph event
   * 
   * @param ge a GraphEvent value
   */
  @SuppressWarnings("unchecked")
  private void notifyGraphListeners(GraphEvent ge) {
    Vector l;
    synchronized (this) {
      l = (Vector) m_graphListeners.clone();
    }
    if (l.size() > 0) {
      for (int i = 0; i < l.size(); i++) {
        ((GraphListener) l.elementAt(i)).acceptGraph(ge);
      }
    }
  }

  /**
   * Notify all batch association rules listeners of a rules event.
   * 
   * @param are a BatchAssociationRulesEvent value
   */
  @SuppressWarnings("unchecked")
  private void notifyRulesListeners(BatchAssociationRulesEvent are) {
    Vector l;

    synchronized (this) {
      l = (Vector) m_rulesListeners.clone();
      for (int i = 0; i < l.size(); i++) {
        l.get(i).acceptAssociationRules(are);
      }
    }
  }

  /**
   * Returns true if, at this time, the object will accept a connection with
   * respect to the named event
   * 
   * @param eventName the event
   * @return true if the object will accept a connection
   */
  @Override
  public boolean connectionAllowed(String eventName) {
    if (m_listenees.containsKey(eventName)) {
      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
   */
  @Override
  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 named event
   * 
   * @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) {

    if (connectionAllowed(eventName)) {
      m_listenees.put(eventName, source);
    }
  }

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

  /**
   * 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 boolean value
   */
  private synchronized void block(boolean tf) {

    if (tf) {
      try {
        // only block if thread is still doing something useful!
        if (m_buildThread.isAlive() && m_state != IDLE) {
          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.
   */
  @Override
  public boolean isBusy() {
    return (m_buildThread != null);
  }

  /**
   * Stop any associator action
   */
  @SuppressWarnings("deprecation")
  @Override
  public void stop() {
    // tell all listenees (upstream beans) to stop
    Enumeration en = m_listenees.keys();
    while (en.hasMoreElements()) {
      Object tempO = m_listenees.get(en.nextElement());
      if (tempO instanceof BeanCommon) {
        ((BeanCommon) tempO).stop();
      }
    }

    // stop the build thread
    if (m_buildThread != null) {
      m_buildThread.interrupt();
      m_buildThread.stop();
      m_buildThread = null;
      m_visual.setStatic();
    }
  }

  /**
   * Set a logger
   * 
   * @param logger a Logger value
   */
  @Override
  public void setLog(Logger logger) {
    m_log = logger;
  }

  /**
   * Return an enumeration of requests that can be made by the user
   * 
   * @return an Enumeration value
   */
  @Override
  public Enumeration enumerateRequests() {
    Vector newVector = new Vector(0);
    if (m_buildThread != null) {
      newVector.addElement("Stop");
    }
    return newVector.elements();
  }

  /**
   * 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("Stop") == 0) {
      stop();
    } else {
      throw new IllegalArgumentException(request
        + " not supported (Associator)");
    }
  }

  /**
   * 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);
  }

  /**
   * 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.
   */
  @Override
  public Instances getStructure(String eventName) {

    Instances structure = null;

    if (eventName.equals("text")) {
      ArrayList attInfo = new ArrayList();
      attInfo.add(new Attribute("Title", (ArrayList) null));
      attInfo.add(new Attribute("Text", (ArrayList) null));
      structure = new Instances("TextEvent", attInfo, 0);
    } else if (eventName.equals("batchAssociationRules")) {
      if (m_Associator != null
        && m_Associator instanceof AssociationRulesProducer) {
        // we make the assumption here that consumers of
        // batchAssociationRules events will utilize a structure
        // consisting of the RHS of the rule (String), LHS of the
        // rule (String) and one numeric attribute for each metric
        // associated with the rules.

        String[] metricNames = ((AssociationRulesProducer) m_Associator)
          .getRuleMetricNames();
        ArrayList attInfo = new ArrayList();
        attInfo.add(new Attribute("LHS", (ArrayList) null));
        attInfo.add(new Attribute("RHS", (ArrayList) null));
        attInfo.add(new Attribute("Support"));
        for (String metricName : metricNames) {
          attInfo.add(new Attribute(metricName));
        }
        structure = new Instances("batchAssociationRulesEvent", attInfo, 0);
      }
    }

    return structure;
  }

  /**
   * 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) {
    if (eventName.compareTo("text") == 0 || eventName.compareTo("graph") == 0
      || eventName.equals("batchAssociationRules")) {
      if (!m_listenees.containsKey("dataSet")
        && !m_listenees.containsKey("trainingSet")) {
        return false;
      }
      Object source = m_listenees.get("trainingSet");
      if (source != null && source instanceof EventConstraints) {
        if (!((EventConstraints) source).eventGeneratable("trainingSet")) {
          return false;
        }
      }
      source = m_listenees.get("dataSet");
      if (source != null && source instanceof EventConstraints) {
        if (!((EventConstraints) source).eventGeneratable("dataSet")) {
          return false;
        }
      }

      if (eventName.compareTo("graph") == 0
        && !(m_Associator instanceof weka.core.Drawable)) {
        return false;
      }

      if (eventName.equals("batchAssociationRules")) {
        if (!(m_Associator instanceof AssociationRulesProducer)) {
          return false;
        }

        if (!((AssociationRulesProducer) m_Associator).canProduceRules()) {
          return false;
        }
      }
    }
    return true;
  }

  private String statusMessagePrefix() {
    return getCustomName()
      + "$"
      + hashCode()
      + "|"
      + ((m_Associator instanceof OptionHandler && Utils.joinOptions(
        ((OptionHandler) m_Associator).getOptions()).length() > 0) ? Utils
        .joinOptions(((OptionHandler) m_Associator).getOptions()) + "|" : "");
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy