Java tutorial
/* * 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 <http://www.gnu.org/licenses/>. */ /* * Clusterer.java * Copyright (C) 2004-2012 University of Waikato, Hamilton, New Zealand * */ package weka.gui.beans; import weka.clusterers.EM; import weka.core.Instances; import weka.core.OptionHandler; import weka.core.Utils; import weka.filters.Filter; import weka.filters.unsupervised.attribute.Remove; import weka.gui.ExtensionFileFilter; import weka.gui.Logger; import weka.gui.WekaFileChooser; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.JPanel; import java.awt.BorderLayout; import java.beans.EventSetDescriptor; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; /** * Bean that wraps around weka.clusterers * * @author <a href="mailto:mutter@cs.waikato.ac.nz">Stefan Mutter</a> * @version $Revision$ * @see JPanel * @see BeanCommon * @see Visible * @see WekaWrapper * @see Serializable * @see UserRequestAcceptor * @see TrainingSetListener * @see TestSetListener */ public class Clusterer extends JPanel implements BeanCommon, Visible, WekaWrapper, EventConstraints, UserRequestAcceptor, TrainingSetListener, TestSetListener, ConfigurationProducer { /** for serialization */ private static final long serialVersionUID = 7729795159836843810L; protected BeanVisual m_visual = new BeanVisual("Clusterer", BeanVisual.ICON_PATH + "EM.gif", BeanVisual.ICON_PATH + "EM_animated.gif"); private static int IDLE = 0; private static int BUILDING_MODEL = 1; private static int CLUSTERING = 2; private int m_state = IDLE; private Thread m_buildThread = null; /** * Global info for the wrapped classifier (if it exists). */ protected String m_globalInfo; /** * Objects talking to us */ private final Hashtable<String, Object> m_listenees = new Hashtable<String, Object>(); /** * Objects listening for batch clusterer events */ private final Vector<BatchClustererListener> m_batchClustererListeners = new Vector<BatchClustererListener>(); /** * Objects listening for graph events */ private final Vector<GraphListener> m_graphListeners = new Vector<GraphListener>(); /** * Objects listening for text events */ private final Vector<TextListener> m_textListeners = new Vector<TextListener>(); /** * Holds training instances for batch training. */ private Instances m_trainingSet; private transient Instances m_testingSet; private weka.clusterers.Clusterer m_Clusterer = new EM(); private transient Logger m_log = null; @SuppressWarnings("unused") private final Double m_dummy = new Double(0.0); private transient WekaFileChooser m_fileChooser = null; /** * Global info (if it exists) for the wrapped classifier * * @return the global info */ public String globalInfo() { return m_globalInfo; } /** * Creates a new <code>Clusterer</code> instance. */ public Clusterer() { setLayout(new BorderLayout()); add(m_visual, BorderLayout.CENTER); setClusterer(m_Clusterer); } /** * 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 clusterer for this wrapper * * @param c a <code>weka.clusterers.Clusterer</code> value */ public void setClusterer(weka.clusterers.Clusterer c) { boolean loadImages = true; if (c.getClass().getName().compareTo(m_Clusterer.getClass().getName()) == 0) { loadImages = false; } else { // clusterer has changed so any batch training status is now // invalid m_trainingSet = null; } m_Clusterer = c; String clustererName = c.getClass().toString(); clustererName = clustererName.substring(clustererName.lastIndexOf('.') + 1, clustererName.length()); if (loadImages) { if (!m_visual.loadIcons(BeanVisual.ICON_PATH + clustererName + ".gif", BeanVisual.ICON_PATH + clustererName + "_animated.gif")) { useDefaultVisual(); } } m_visual.setText(clustererName); // get global info m_globalInfo = KnowledgeFlowApp.getGlobalInfo(m_Clusterer); } /** * Returns true if this clusterer has an incoming connection that is a batch * set of instances * * @return a <code>boolean</code> value */ public boolean hasIncomingBatchInstances() { if (m_listenees.size() == 0) { return false; } if (m_listenees.containsKey("trainingSet") || m_listenees.containsKey("testSet") || m_listenees.containsKey("dataSet")) { return true; } return false; } /** * Get the clusterer currently set for this wrapper * * @return a <code>weka.clusterers.Clusterer</code> value */ public weka.clusterers.Clusterer getClusterer() { return m_Clusterer; } /** * Sets the algorithm (clusterer) for this bean * * @param algorithm an <code>Object</code> value * @exception IllegalArgumentException if an error occurs */ @Override public void setWrappedAlgorithm(Object algorithm) { if (!(algorithm instanceof weka.clusterers.Clusterer)) { throw new IllegalArgumentException( algorithm.getClass() + " : incorrect " + "type of algorithm (Clusterer)"); } setClusterer((weka.clusterers.Clusterer) algorithm); } /** * Returns the wrapped clusterer * * @return an <code>Object</code> value */ @Override public Object getWrappedAlgorithm() { return getClusterer(); } /** * Accepts a training set and builds batch clusterer * * @param e a <code>TrainingSetEvent</code> value */ @Override public void acceptTrainingSet(final TrainingSetEvent e) { if (e.isStructureOnly()) { // no need to build a clusterer, instead just generate a dummy // BatchClustererEvent in order to pass on instance structure to // any listeners BatchClustererEvent ce = new BatchClustererEvent(this, m_Clusterer, new DataSetEvent(this, e.getTrainingSet()), e.getSetNumber(), e.getMaxSetNumber(), 1); notifyBatchClustererListeners(ce); return; } if (m_buildThread == null) { try { if (m_state == IDLE) { synchronized (this) { m_state = BUILDING_MODEL; } m_trainingSet = e.getTrainingSet(); // final String oldText = m_visual.getText(); m_buildThread = new Thread() { @SuppressWarnings("deprecation") @Override public void run() { try { if (m_trainingSet != null) { m_visual.setAnimated(); // m_visual.setText("Building clusters..."); if (m_log != null) { m_log.statusMessage(statusMessagePrefix() + "Building clusters..."); } buildClusterer(); if (m_batchClustererListeners.size() > 0) { BatchClustererEvent ce = new BatchClustererEvent(this, m_Clusterer, new DataSetEvent(this, e.getTrainingSet()), e.getSetNumber(), e.getMaxSetNumber(), 1); notifyBatchClustererListeners(ce); } if (m_Clusterer instanceof weka.core.Drawable && m_graphListeners.size() > 0) { String grphString = ((weka.core.Drawable) m_Clusterer).graph(); int grphType = ((weka.core.Drawable) m_Clusterer).graphType(); String grphTitle = m_Clusterer.getClass().getName(); grphTitle = grphTitle.substring(grphTitle.lastIndexOf('.') + 1, grphTitle.length()); grphTitle = "Set " + e.getSetNumber() + " (" + e.getTrainingSet().relationName() + ") " + grphTitle; GraphEvent ge = new GraphEvent(Clusterer.this, grphString, grphTitle, grphType); notifyGraphListeners(ge); } if (m_textListeners.size() > 0) { String modelString = m_Clusterer.toString(); String titleString = m_Clusterer.getClass().getName(); titleString = titleString.substring(titleString.lastIndexOf('.') + 1, titleString.length()); modelString = "=== Clusterer model ===\n\n" + "Scheme: " + titleString + "\n" + "Relation: " + m_trainingSet.relationName() + ((e.getMaxSetNumber() > 1) ? "\nTraining Fold: " + e.getSetNumber() : "") + "\n\n" + modelString; titleString = "Model: " + titleString; TextEvent nt = new TextEvent(Clusterer.this, modelString, titleString); notifyTextListeners(nt); } } } catch (Exception ex) { Clusterer.this.stop(); // stop processing if (m_log != null) { m_log.statusMessage(statusMessagePrefix() + "ERROR (See log for details"); m_log.logMessage("[Clusterer] " + statusMessagePrefix() + " problem training clusterer. " + ex.getMessage()); } ex.printStackTrace(); } finally { // m_visual.setText(oldText); m_visual.setStatic(); m_state = IDLE; if (isInterrupted()) { // prevent any clusterer events from being fired m_trainingSet = null; if (m_log != null) { m_log.logMessage("[Clusterer]" + statusMessagePrefix() + " Build clusterer interrupted!"); m_log.statusMessage(statusMessagePrefix() + "INTERRUPTED"); } } else { // save header m_trainingSet = new Instances(m_trainingSet, 0); 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(); } } } /** * Accepts a test set for a batch trained clusterer * * @param e a <code>TestSetEvent</code> value */ @Override public void acceptTestSet(TestSetEvent e) { if (m_trainingSet != null) { try { if (m_state == IDLE) { synchronized (this) { m_state = CLUSTERING; } m_testingSet = e.getTestSet(); if (m_trainingSet.equalHeaders(m_testingSet)) { BatchClustererEvent ce = new BatchClustererEvent(this, m_Clusterer, new DataSetEvent(this, e.getTestSet()), e.getSetNumber(), e.getMaxSetNumber(), 0); notifyBatchClustererListeners(ce); } m_state = IDLE; } } catch (Exception ex) { stop(); // stop any processing if (m_log != null) { m_log.statusMessage(statusMessagePrefix() + "ERROR (see log for details"); m_log.logMessage( "[Clusterer] " + statusMessagePrefix() + " problem during testing. " + ex.getMessage()); } ex.printStackTrace(); } } } /** * Builds the clusters */ private void buildClusterer() throws Exception { if (m_trainingSet.classIndex() < 0) { m_Clusterer.buildClusterer(m_trainingSet); } else { // class based evaluation if class attribute is set Remove removeClass = new Remove(); removeClass.setAttributeIndices("" + (m_trainingSet.classIndex() + 1)); removeClass.setInvertSelection(false); removeClass.setInputFormat(m_trainingSet); Instances clusterTrain = Filter.useFilter(m_trainingSet, removeClass); m_Clusterer.buildClusterer(clusterTrain); } } /** * Sets the visual appearance of this wrapper bean * * @param newVisual a <code>BeanVisual</code> 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 + "DefaultClusterer.gif", BeanVisual.ICON_PATH + "DefaultClusterer_animated.gif"); } /** * Add a batch clusterer listener * * @param cl a <code>BatchClustererListener</code> value */ public synchronized void addBatchClustererListener(BatchClustererListener cl) { m_batchClustererListeners.addElement(cl); } /** * Remove a batch clusterer listener * * @param cl a <code>BatchClustererListener</code> value */ public synchronized void removeBatchClustererListener(BatchClustererListener cl) { m_batchClustererListeners.remove(cl); } /** * Notify all batch clusterer listeners of a batch clusterer event * * @param ce a <code>BatchClustererEvent</code> value */ @SuppressWarnings("unchecked") private void notifyBatchClustererListeners(BatchClustererEvent ce) { Vector<BatchClustererListener> l; synchronized (this) { l = (Vector<BatchClustererListener>) m_batchClustererListeners.clone(); } if (l.size() > 0) { for (int i = 0; i < l.size(); i++) { l.elementAt(i).acceptClusterer(ce); } } } /** * Add a graph listener * * @param cl a <code>GraphListener</code> value */ public synchronized void addGraphListener(GraphListener cl) { m_graphListeners.addElement(cl); } /** * Remove a graph listener * * @param cl a <code>GraphListener</code> value */ public synchronized void removeGraphListener(GraphListener cl) { m_graphListeners.remove(cl); } /** * Notify all graph listeners of a graph event * * @param ge a <code>GraphEvent</code> value */ @SuppressWarnings("unchecked") private void notifyGraphListeners(GraphEvent ge) { Vector<GraphListener> l; synchronized (this) { l = (Vector<GraphListener>) m_graphListeners.clone(); } if (l.size() > 0) { for (int i = 0; i < l.size(); i++) { l.elementAt(i).acceptGraph(ge); } } } /** * Add a text listener * * @param cl a <code>TextListener</code> value */ public synchronized void addTextListener(TextListener cl) { m_textListeners.addElement(cl); } /** * Remove a text listener * * @param cl a <code>TextListener</code> value */ public synchronized void removeTextListener(TextListener cl) { m_textListeners.remove(cl); } /** * Notify all text listeners of a text event * * @param ge a <code>TextEvent</code> value */ @SuppressWarnings("unchecked") private void notifyTextListeners(TextEvent ge) { Vector<TextListener> l; synchronized (this) { l = (Vector<TextListener>) m_textListeners.clone(); } if (l.size() > 0) { for (int i = 0; i < l.size(); i++) { l.elementAt(i).acceptText(ge); } } } /** * 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) { } /** * 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 (eventName.compareTo("instance") == 0) { if (!(m_Clusterer instanceof * weka.classifiers.UpdateableClassifier)) { return false; } } */ 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); /* * if (eventName.compareTo("instance") == 0) { startIncrementalHandler(); * } */ } } /** * 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 * clusterer 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 { // 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 clusterer action */ @SuppressWarnings("deprecation") @Override public void stop() { // tell all listenees (upstream beans) to stop Enumeration<String> 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 <code>Logger</code> value */ @Override public void setLog(Logger logger) { m_log = logger; } public void saveModel() { try { if (m_fileChooser == null) { // i.e. after de-serialization m_fileChooser = new WekaFileChooser(new File(System.getProperty("user.dir"))); ExtensionFileFilter ef = new ExtensionFileFilter("model", "Serialized weka clusterer"); m_fileChooser.setFileFilter(ef); } int returnVal = m_fileChooser.showSaveDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { File saveTo = m_fileChooser.getSelectedFile(); String fn = saveTo.getAbsolutePath(); if (!fn.endsWith(".model")) { fn += ".model"; saveTo = new File(fn); } ObjectOutputStream os = new ObjectOutputStream( new BufferedOutputStream(new FileOutputStream(saveTo))); os.writeObject(m_Clusterer); if (m_trainingSet != null) { Instances header = m_trainingSet.stringFreeStructure(); os.writeObject(header); } os.close(); if (m_log != null) { m_log.logMessage("[Clusterer] Saved clusterer " + getCustomName()); } } } catch (Exception ex) { JOptionPane.showMessageDialog(Clusterer.this, "Problem saving clusterer.\n", "Save Model", JOptionPane.ERROR_MESSAGE); if (m_log != null) { m_log.logMessage("[Clusterer] Problem saving clusterer. " + getCustomName() + ex.getMessage()); } } } public void loadModel() { try { if (m_fileChooser == null) { // i.e. after de-serialization m_fileChooser = new WekaFileChooser(new File(System.getProperty("user.dir"))); ExtensionFileFilter ef = new ExtensionFileFilter("model", "Serialized weka clusterer"); m_fileChooser.setFileFilter(ef); } int returnVal = m_fileChooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { File loadFrom = m_fileChooser.getSelectedFile(); ObjectInputStream is = new ObjectInputStream( new BufferedInputStream(new FileInputStream(loadFrom))); // try and read the model weka.clusterers.Clusterer temp = (weka.clusterers.Clusterer) is.readObject(); // Update name and icon setClusterer(temp); // try and read the header (if present) try { m_trainingSet = (Instances) is.readObject(); } catch (Exception ex) { // quietly ignore } is.close(); if (m_log != null) { m_log.logMessage("[Clusterer] Loaded clusterer: " + m_Clusterer.getClass().toString()); } } } catch (Exception ex) { JOptionPane.showMessageDialog(Clusterer.this, "Problem loading classifier.\n", "Load Model", JOptionPane.ERROR_MESSAGE); if (m_log != null) { m_log.logMessage("[Clusterer] Problem loading classifier. " + ex.getMessage()); } } } /** * Return an enumeration of requests that can be made by the user * * @return an <code>Enumeration</code> value */ @Override public Enumeration<String> enumerateRequests() { Vector<String> newVector = new Vector<String>(0); if (m_buildThread != null) { newVector.addElement("Stop"); } if (m_buildThread == null && m_Clusterer != null) { newVector.addElement("Save model"); } if (m_buildThread == null) { newVector.addElement("Load model"); } 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 if (request.compareTo("Save model") == 0) { saveModel(); } else if (request.compareTo("Load model") == 0) { loadModel(); } else { throw new IllegalArgumentException(request + " not supported (Clusterer)"); } } /** * 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 */ @Override public boolean eventGeneratable(String eventName) { if (eventName.compareTo("graph") == 0) { // can't generate a GraphEvent if clusterer is not drawable if (!(m_Clusterer instanceof weka.core.Drawable)) { return false; } // need to have a training set before the clusterer // can generate a graph! if (!m_listenees.containsKey("trainingSet")) { return false; } // Source needs to be able to generate a trainingSet // before we can generate a graph Object source = m_listenees.get("trainingSet"); if (source instanceof EventConstraints) { if (!((EventConstraints) source).eventGeneratable("trainingSet")) { return false; } } } if (eventName.compareTo("batchClusterer") == 0) { if (!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; } } } if (eventName.compareTo("text") == 0) { if (!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; } } } if (eventName.compareTo("batchClassifier") == 0) { return false; } if (eventName.compareTo("incrementalClassifier") == 0) { return false; } return true; } private String statusMessagePrefix() { return getCustomName() + "$" + hashCode() + "|" + ((m_Clusterer instanceof OptionHandler && Utils.joinOptions(((OptionHandler) m_Clusterer).getOptions()).length() > 0) ? Utils.joinOptions(((OptionHandler) m_Clusterer).getOptions()) + "|" : ""); } }