/*
* 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.
*/
/*
* Loader.java
* Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
*
*/
package weka.gui.beans;
import weka.core.Environment;
import weka.core.EnvironmentHandler;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.OptionHandler;
import weka.core.Utils;
import weka.core.converters.ArffLoader;
import weka.core.converters.DatabaseLoader;
import weka.core.converters.FileSourcedConverter;
import weka.gui.Logger;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.EventSetDescriptor;
import java.beans.beancontext.BeanContext;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.util.Enumeration;
import java.util.Vector;
import javax.swing.JButton;
/**
* Loads data sets using weka.core.converter classes
*
* @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
* @version $Revision: 7340 $
* @since 1.0
* @see AbstractDataSource
* @see UserRequestAcceptor
*/
public class Loader
extends AbstractDataSource
implements Startable, /*UserRequestAcceptor,*/ WekaWrapper,
EventConstraints, BeanCommon, EnvironmentHandler,
StructureProducer {
/** for serialization */
private static final long serialVersionUID = 1993738191961163027L;
/**
* Holds the instances loaded
*/
private transient Instances m_dataSet;
/**
* Holds the format of the last loaded data set
*/
private transient Instances m_dataFormat;
/**
* Global info for the wrapped loader (if it exists).
*/
protected String m_globalInfo;
/**
* Thread for doing IO in
*/
private LoadThread m_ioThread;
private static int IDLE = 0;
private static int BATCH_LOADING = 1;
private static int INCREMENTAL_LOADING = 2;
private int m_state = IDLE;
/**
* Loader
*/
private weka.core.converters.Loader m_Loader = new ArffLoader();
private InstanceEvent m_ie = new InstanceEvent(this);
/**
* Keep track of how many listeners for different types of events there are.
*/
private int m_instanceEventTargets = 0;
private int m_dataSetEventTargets = 0;
/** Flag indicating that a database has already been configured*/
private boolean m_dbSet = false;
/**
* Logging
*/
protected transient Logger m_log;
/**
* The environment variables.
*/
protected transient Environment m_env;
/**
* Asked to stop?
*/
protected boolean m_stopped = false;
private class LoadThread extends Thread {
private DataSource m_DP;
public LoadThread(DataSource dp) {
m_DP = dp;
}
public void run() {
try {
m_visual.setAnimated();
// m_visual.setText("Loading...");
boolean instanceGeneration = true;
// determine if we are going to produce data set or instance events
/* for (int i = 0; i < m_listeners.size(); i++) {
if (m_listeners.elementAt(i) instanceof DataSourceListener) {
instanceGeneration = false;
break;
}
} */
if (m_dataSetEventTargets > 0) {
instanceGeneration = false;
m_state = BATCH_LOADING;
}
// Set environment variables
if (m_Loader instanceof EnvironmentHandler && m_env != null) {
((EnvironmentHandler)m_Loader).setEnvironment(m_env);
}
String msg = statusMessagePrefix();
if (m_Loader instanceof FileSourcedConverter) {
msg += "Loading " + ((FileSourcedConverter)m_Loader).retrieveFile().getName();
} else {
msg += "Loading...";
}
if (m_log != null) {
m_log.statusMessage(msg);
}
if (instanceGeneration) {
m_state = INCREMENTAL_LOADING;
// boolean start = true;
Instance nextInstance = null;
// load and pass on the structure first
Instances structure = null;
try {
m_Loader.reset();
// System.err.println("NOTIFYING STRUCTURE AVAIL");
structure = m_Loader.getStructure();
notifyStructureAvailable(structure);
} catch (IOException e) {
if (m_log != null) {
m_log.statusMessage(statusMessagePrefix()
+"ERROR (See log for details");
m_log.logMessage("[Loader] " + statusMessagePrefix()
+ " " + e.getMessage());
}
e.printStackTrace();
}
try {
nextInstance = m_Loader.getNextInstance(structure);
} catch (IOException e) {
if (m_log != null) {
m_log.statusMessage(statusMessagePrefix()
+"ERROR (See log for details");
m_log.logMessage("[Loader] " + statusMessagePrefix()
+ " " + e.getMessage());
}
e.printStackTrace();
}
int z = 0;
while (nextInstance != null) {
if (m_stopped) {
break;
}
nextInstance.setDataset(structure);
// format.add(nextInstance);
/* InstanceEvent ie = (start)
? new InstanceEvent(m_DP, nextInstance,
InstanceEvent.FORMAT_AVAILABLE)
: new InstanceEvent(m_DP, nextInstance,
InstanceEvent.INSTANCE_AVAILABLE); */
// if (start) {
// m_ie.setStatus(InstanceEvent.FORMAT_AVAILABLE);
// } else {
m_ie.setStatus(InstanceEvent.INSTANCE_AVAILABLE);
// }
m_ie.setInstance(nextInstance);
// start = false;
// System.err.println(z);
nextInstance = m_Loader.getNextInstance(structure);
if (nextInstance == null) {
m_ie.setStatus(InstanceEvent.BATCH_FINISHED);
}
notifyInstanceLoaded(m_ie);
z++;
if (z % 10000 == 0) {
// m_visual.setText("" + z + " instances...");
if (m_log != null) {
m_log.statusMessage(statusMessagePrefix()
+ "Loaded " + z + " instances");
}
}
}
m_visual.setStatic();
// m_visual.setText(structure.relationName());
} else {
m_Loader.reset();
m_dataSet = m_Loader.getDataSet();
m_visual.setStatic();
if (m_log != null) {
m_log.logMessage("[Loader] " + statusMessagePrefix()
+ " loaded " + m_dataSet.relationName());
}
// m_visual.setText(m_dataSet.relationName());
notifyDataSetLoaded(new DataSetEvent(m_DP, m_dataSet));
}
} catch (Exception ex) {
if (m_log != null) {
m_log.statusMessage(statusMessagePrefix()
+"ERROR (See log for details");
m_log.logMessage("[Loader] " + statusMessagePrefix()
+ " " + ex.getMessage());
}
ex.printStackTrace();
} finally {
if (Thread.currentThread().isInterrupted()) {
if (m_log != null) {
m_log.logMessage("[Loader] " + statusMessagePrefix()
+ " loading interrupted!");
}
}
m_ioThread = null;
// m_visual.setText("Finished");
// m_visual.setIcon(m_inactive.getVisual());
m_visual.setStatic();
m_state = IDLE;
m_stopped = false;
if (m_log != null) {
m_log.statusMessage(statusMessagePrefix() + "Finished.");
}
block(false);
}
}
}
/**
* Global info (if it exists) for the wrapped loader
*
* @return the global info
*/
public String globalInfo() {
return m_globalInfo;
}
public Loader() {
super();
setLoader(m_Loader);
appearanceFinal();
}
public void setDB(boolean flag){
m_dbSet = flag;
}
protected void appearanceFinal() {
removeAll();
setLayout(new BorderLayout());
JButton goButton = new JButton("Start...");
add(goButton, BorderLayout.CENTER);
goButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
startLoading();
}
});
}
protected void appearanceDesign() {
removeAll();
setLayout(new BorderLayout());
add(m_visual, BorderLayout.CENTER);
}
/**
* Set a bean context for this bean
*
* @param bc a <code>BeanContext</code> value
*/
public void setBeanContext(BeanContext bc) {
super.setBeanContext(bc);
if (m_design) {
appearanceDesign();
} else {
appearanceFinal();
}
}
/**
* Set the loader to use
*
* @param loader a <code>weka.core.converters.Loader</code> value
*/
public void setLoader(weka.core.converters.Loader loader) {
boolean loadImages = true;
if (loader.getClass().getName().
compareTo(m_Loader.getClass().getName()) == 0) {
loadImages = false;
}
m_Loader = loader;
String loaderName = loader.getClass().toString();
loaderName = loaderName.substring(loaderName.
lastIndexOf('.')+1,
loaderName.length());
if (loadImages) {
if (m_Loader instanceof Visible) {
m_visual = ((Visible) m_Loader).getVisual();
} else {
if (!m_visual.loadIcons(BeanVisual.ICON_PATH+loaderName+".gif",
BeanVisual.ICON_PATH+loaderName+"_animated.gif")) {
useDefaultVisual();
}
}
}
m_visual.setText(loaderName);
// get global info
m_globalInfo = KnowledgeFlowApp.getGlobalInfo(m_Loader);
}
protected void newFileSelected() throws Exception {
if(! (m_Loader instanceof DatabaseLoader)) {
newStructure();
/* // try to load structure (if possible) and notify any listeners
// Set environment variables
if (m_Loader instanceof EnvironmentHandler && m_env != null) {
try {
((EnvironmentHandler)m_Loader).setEnvironment(m_env);
}catch (Exception ex) {
}
}
m_dataFormat = m_Loader.getStructure();
// System.err.println(m_dataFormat);
System.out.println("[Loader] Notifying listeners of instance structure avail.");
notifyStructureAvailable(m_dataFormat); */
}
}
protected void newStructure() throws Exception {
// Set environment variables
if (m_Loader instanceof EnvironmentHandler && m_env != null) {
try {
((EnvironmentHandler)m_Loader).setEnvironment(m_env);
}catch (Exception ex) {
}
}
m_dataFormat = m_Loader.getStructure();
System.out.println("[Loader] Notifying listeners of instance structure avail.");
notifyStructureAvailable(m_dataFormat);
}
/**
* Get the structure of the output encapsulated in the named
* event. If the structure can't be determined in advance of
* seeing input, or this StructureProducer does not generate
* the named event, null should be returned.
*
* @param eventName the name of the output event that encapsulates
* the requested output.
*
* @return the structure of the output encapsulated in the named
* event or null if it can't be determined in advance of seeing input
* or the named event is not generated by this StructureProduce.
*/
public Instances getStructure(String eventName) {
if (!eventName.equals("dataSet") && !eventName.equals("instance")) {
return null;
}
if (m_dataSetEventTargets > 0 && !eventName.equals("dataSet")) {
return null;
}
if (m_dataSetEventTargets == 0 && !eventName.equals("instance")) {
return null;
}
try {
newStructure();
} catch (Exception ex) {
//ex.printStackTrace();
System.err.println("[KnowledgeFlow/Loader] Warning: " + ex.getMessage());
m_dataFormat = null;
}
return m_dataFormat;
}
/**
* Get the loader
*
* @return a <code>weka.core.converters.Loader</code> value
*/
public weka.core.converters.Loader getLoader() {
return m_Loader;
}
/**
* Set the loader
*
* @param algorithm a Loader
* @exception IllegalArgumentException if an error occurs
*/
public void setWrappedAlgorithm(Object algorithm)
{
if (!(algorithm instanceof weka.core.converters.Loader)) {
throw new IllegalArgumentException(algorithm.getClass()+" : incorrect "
+"type of algorithm (Loader)");
}
setLoader((weka.core.converters.Loader)algorithm);
}
/**
* Get the loader
*
* @return a Loader
*/
public Object getWrappedAlgorithm() {
return getLoader();
}
/**
* Notify all listeners that the structure of a data set
* is available.
*
* @param structure an <code>Instances</code> value
*/
protected void notifyStructureAvailable(Instances structure) {
if (m_dataSetEventTargets > 0 && structure != null) {
DataSetEvent dse = new DataSetEvent(this, structure);
notifyDataSetLoaded(dse);
} else if (m_instanceEventTargets > 0 && structure != null) {
m_ie.setStructure(structure);
notifyInstanceLoaded(m_ie);
}
}
/**
* Notify all Data source listeners that a data set has been loaded
*
* @param e a <code>DataSetEvent</code> value
*/
protected void notifyDataSetLoaded(DataSetEvent e) {
Vector l;
synchronized (this) {
l = (Vector)m_listeners.clone();
}
if (l.size() > 0) {
for(int i = 0; i < l.size(); i++) {
((DataSourceListener)l.elementAt(i)).acceptDataSet(e);
}
m_dataSet = null;
}
}
/**
* Notify all instance listeners that a new instance is available
*
* @param e an <code>InstanceEvent</code> value
*/
protected void notifyInstanceLoaded(InstanceEvent e) {
Vector l;
synchronized (this) {
l = (Vector)m_listeners.clone();
}
if (l.size() > 0) {
for(int i = 0; i < l.size(); i++) {
((InstanceListener)l.elementAt(i)).acceptInstance(e);
}
m_dataSet = null;
}
}
/**
* Start loading data
*/
public void startLoading() {
if (m_ioThread == null) {
// m_visual.setText(m_dataSetFile.getName());
m_state = BATCH_LOADING;
m_ioThread = new LoadThread(Loader.this);
m_ioThread.setPriority(Thread.MIN_PRIORITY);
m_ioThread.start();
} else {
m_ioThread = null;
m_state = IDLE;
}
}
/**
* Get a list of user requests
*
* @return an <code>Enumeration</code> value
*/
/*public Enumeration enumerateRequests() {
Vector newVector = new Vector(0);
boolean ok = true;
if (m_ioThread == null) {
if (m_Loader instanceof FileSourcedConverter) {
String temp = ((FileSourcedConverter) m_Loader).retrieveFile().getPath();
Environment env = (m_env == null) ? Environment.getSystemWide() : m_env;
try {
temp = env.substitute(temp);
} catch (Exception ex) {}
File tempF = new File(temp);
if (!tempF.isFile()) {
ok = false;
}
}
String entry = "Start loading";
if (!ok) {
entry = "$"+entry;
}
newVector.addElement(entry);
}
return newVector.elements();
} */
/**
* Perform the named request
*
* @param request a <code>String</code> value
* @exception IllegalArgumentException if an error occurs
*/
/*public void performRequest(String request) {
if (request.compareTo("Start loading") == 0) {
startLoading();
} else {
throw new IllegalArgumentException(request
+ " not supported (Loader)");
}
} */
/**
* Start loading
*
* @exception Exception if something goes wrong
*/
public void start() throws Exception {
startLoading();
block(true);
}
/**
* Gets a string that describes the start action. The
* KnowledgeFlow uses this in the popup contextual menu
* for the component. The string can be proceeded by
* a '$' character to indicate that the component can't
* be started at present.
*
* @return a string describing the start action.
*/
public String getStartMessage() {
boolean ok = true;
String entry = "Start loading";
if (m_ioThread == null) {
if (m_Loader instanceof FileSourcedConverter) {
String temp = ((FileSourcedConverter) m_Loader).retrieveFile().getPath();
Environment env = (m_env == null) ? Environment.getSystemWide() : m_env;
try {
temp = env.substitute(temp);
} catch (Exception ex) {}
File tempF = new File(temp);
if (!tempF.isFile()) {
ok = false;
}
}
if (!ok) {
entry = "$"+entry;
}
}
return entry;
}
/**
* 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 <code>boolean</code> value
*/
private synchronized void block(boolean tf) {
if (tf) {
try {
// only block if thread is still doing something useful!
if (m_ioThread.isAlive() && m_state != IDLE) {
wait();
}
} catch (InterruptedException ex) {
}
} else {
notifyAll();
}
}
/**
* Returns true if the named event can be generated at this time
*
* @param eventName the event
* @return a <code>boolean</code> value
*/
public boolean eventGeneratable(String eventName) {
if (eventName.compareTo("instance") == 0) {
if (!(m_Loader instanceof weka.core.converters.IncrementalConverter)) {
return false;
}
if (m_dataSetEventTargets > 0) {
return false;
}
/* for (int i = 0; i < m_listeners.size(); i++) {
if (m_listeners.elementAt(i) instanceof DataSourceListener) {
return false;
}
} */
}
if (eventName.compareTo("dataSet") == 0) {
if (!(m_Loader instanceof weka.core.converters.BatchConverter)) {
return false;
}
if (m_instanceEventTargets > 0) {
return false;
}
/* for (int i = 0; i < m_listeners.size(); i++) {
if (m_listeners.elementAt(i) instanceof InstanceListener) {
return false;
}
} */
}
return true;
}
/**
* Add a listener
*
* @param dsl a <code>DataSourceListener</code> value
*/
public synchronized void addDataSourceListener(DataSourceListener dsl) {
super.addDataSourceListener(dsl);
m_dataSetEventTargets ++;
// pass on any current instance format
try{
if((m_Loader instanceof DatabaseLoader && m_dbSet && m_dataFormat == null) ||
(!(m_Loader instanceof DatabaseLoader) && m_dataFormat == null)) {
m_dataFormat = m_Loader.getStructure();
m_dbSet = false;
}
}catch(Exception ex){
}
notifyStructureAvailable(m_dataFormat);
}
/**
* Remove a listener
*
* @param dsl a <code>DataSourceListener</code> value
*/
public synchronized void removeDataSourceListener(DataSourceListener dsl) {
super.removeDataSourceListener(dsl);
m_dataSetEventTargets --;
}
/**
* Add an instance listener
*
* @param dsl a <code>InstanceListener</code> value
*/
public synchronized void addInstanceListener(InstanceListener dsl) {
super.addInstanceListener(dsl);
m_instanceEventTargets ++;
try{
if((m_Loader instanceof DatabaseLoader && m_dbSet && m_dataFormat == null) ||
(!(m_Loader instanceof DatabaseLoader) && m_dataFormat == null)) {
m_dataFormat = m_Loader.getStructure();
m_dbSet = false;
}
}catch(Exception ex){
}
// pass on any current instance format
notifyStructureAvailable(m_dataFormat);
}
/**
* Remove an instance listener
*
* @param dsl a <code>InstanceListener</code> value
*/
public synchronized void removeInstanceListener(InstanceListener dsl) {
super.removeInstanceListener(dsl);
m_instanceEventTargets --;
}
public static void main(String [] args) {
try {
final javax.swing.JFrame jf = new javax.swing.JFrame();
jf.getContentPane().setLayout(new java.awt.BorderLayout());
final Loader tv = new Loader();
jf.getContentPane().add(tv, java.awt.BorderLayout.CENTER);
jf.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent e) {
jf.dispose();
System.exit(0);
}
});
jf.setSize(800,600);
jf.setVisible(true);
} catch (Exception ex) {
ex.printStackTrace();
}
}
private Object readResolve() throws ObjectStreamException {
// try and reset the Loader
if (m_Loader != null) {
try {
m_Loader.reset();
} catch (Exception ex) {
}
}
return this;
}
/**
* 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();
}
/**
* Set a logger
*
* @param logger a <code>weka.gui.Logger</code> value
*/
public void setLog(Logger logger) {
m_log = logger;
}
/**
* Set environment variables to use.
*
* @param env the environment variables to
* use
*/
public void setEnvironment(Environment env) {
m_env = env;
}
/**
* Returns true if, at this time,
* the object will accept a connection via the supplied
* EventSetDescriptor. Always returns false for loader.
*
* @param esd the EventSetDescriptor
* @return true if the object will accept a connection
*/
public boolean connectionAllowed(EventSetDescriptor esd) {
return false;
}
/**
* Returns true if, at this time,
* the object will accept a connection via the named event
*
* @param eventName the name of the event
* @return true if the object will accept a connection
*/
public boolean connectionAllowed(String eventName) {
return false;
}
/**
* Notify this object that it has been registered as a listener with
* a source for receiving events described by the named event
* This object is responsible for recording this fact.
*
* @param eventName the event
* @param source the source with which this object has been registered as
* a listener
*/
public void connectionNotification(String eventName, Object source) {
// this should never get called for us.
}
/**
* Notify this object that it has been deregistered as a listener with
* a source for named event. This object is responsible
* for recording this fact.
*
* @param eventName the event
* @param source the source with which this object has been registered as
* a listener
*/
public void disconnectionNotification(String eventName, Object source) {
// this should never get called for us.
}
/**
* Stop any loading action.
*/
public void stop() {
m_stopped = true;
}
/**
* Returns true if. at this time, the bean is busy with some
* (i.e. perhaps a worker thread is performing some calculation).
*
* @return true if the bean is busy.
*/
public boolean isBusy() {
return (m_ioThread != null);
}
private String statusMessagePrefix() {
return getCustomName() + "$" + hashCode() + "|"
+ ((m_Loader instanceof OptionHandler)
? Utils.joinOptions(((OptionHandler)m_Loader).getOptions()) + "|"
: "");
}
// Custom de-serialization in order to set default
// environment variables on de-serialization
private void readObject(ObjectInputStream aStream)
throws IOException, ClassNotFoundException {
aStream.defaultReadObject();
// set a default environment to use
m_env = Environment.getSystemWide();
}
}