2
* This program is free software; you can redistribute it and/or modify
3
* it under the terms of the GNU General Public License as published by
4
* the Free Software Foundation; either version 2 of the License, or
5
* (at your option) any later version.
7
* This program is distributed in the hope that it will be useful,
8
* but WITHOUT ANY WARRANTY; without even the implied warranty of
9
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
* GNU General Public License for more details.
12
* You should have received a copy of the GNU General Public License
13
* along with this program; if not, write to the Free Software
14
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
* Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
23
package weka.gui.beans;
25
import weka.core.Instance;
26
import weka.core.Instances;
27
import weka.filters.AllFilter;
28
import weka.filters.StreamableFilter;
29
import weka.filters.SupervisedFilter;
30
import weka.gui.Logger;
32
import java.awt.BorderLayout;
33
import java.beans.EventSetDescriptor;
34
import java.io.Serializable;
35
import java.util.Enumeration;
36
import java.util.EventObject;
37
import java.util.Hashtable;
38
import java.util.Vector;
40
import javax.swing.JPanel;
43
* A wrapper bean for Weka filters
45
* @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
46
* @version $Revision: 1.16 $
50
implements BeanCommon, Visible, WekaWrapper,
51
Serializable, UserRequestAcceptor,
52
TrainingSetListener, TestSetListener,
53
TrainingSetProducer, TestSetProducer,
54
DataSource, DataSourceListener,
55
InstanceListener, EventConstraints {
57
/** for serialization */
58
private static final long serialVersionUID = 8249759470189439321L;
60
protected BeanVisual m_visual =
61
new BeanVisual("Filter",
62
BeanVisual.ICON_PATH+"DefaultFilter.gif",
63
BeanVisual.ICON_PATH+"DefaultFilter_animated.gif");
65
private static int IDLE = 0;
66
private static int FILTERING_TRAINING = 1;
67
private static int FILTERING_TEST = 2;
68
private int m_state = IDLE;
70
protected Thread m_filterThread = null;
72
private transient Instances m_trainingSet;
73
private transient Instances m_testingSet;
76
* Global info for the wrapped filter (if it exists).
78
protected String m_globalInfo;
81
* Objects talking to us
83
private Hashtable m_listenees = new Hashtable();
86
* Objects listening for training set events
88
private Vector m_trainingListeners = new Vector();
91
* Objects listening for test set events
93
private Vector m_testListeners = new Vector();
96
* Objects listening for instance events
98
private Vector m_instanceListeners = new Vector();
101
* Objects listening for data set events
103
private Vector m_dataListeners = new Vector();
108
private weka.filters.Filter m_Filter = new AllFilter();
111
* Instance event object for passing on filtered instance streams
113
private InstanceEvent m_ie = new InstanceEvent(this);
115
private transient Logger m_log = null;
118
* Global info (if it exists) for the wrapped filter
120
* @return the global info
122
public String globalInfo() {
127
setLayout(new BorderLayout());
128
add(m_visual, BorderLayout.CENTER);
133
* Set the filter to be wrapped by this bean
135
* @param c a <code>weka.filters.Filter</code> value
137
public void setFilter(weka.filters.Filter c) {
138
boolean loadImages = true;
139
if (c.getClass().getName().
140
compareTo(m_Filter.getClass().getName()) == 0) {
144
String filterName = c.getClass().toString();
145
filterName = filterName.substring(filterName.
147
filterName.length());
149
if (m_Filter instanceof Visible) {
150
m_visual = ((Visible) m_Filter).getVisual();
152
if (!m_visual.loadIcons(BeanVisual.ICON_PATH+filterName+".gif",
153
BeanVisual.ICON_PATH+filterName+"_animated.gif")) {
158
m_visual.setText(filterName.substring(filterName.lastIndexOf('.')+1,
159
filterName.length()));
161
if (m_Filter instanceof LogWriter && m_log != null) {
162
((LogWriter) m_Filter).setLog(m_log);
165
if (!(m_Filter instanceof StreamableFilter) &&
166
(m_listenees.containsKey("instance"))) {
168
m_log.logMessage("WARNING : "+m_Filter.getClass().getName()
169
+" is not an incremental filter");
174
m_globalInfo = KnowledgeFlowApp.getGlobalInfo(m_Filter);
177
public weka.filters.Filter getFilter() {
182
* Set the filter to be wrapped by this bean
184
* @param algorithm a weka.filters.Filter
185
* @exception IllegalArgumentException if an error occurs
187
public void setWrappedAlgorithm(Object algorithm) {
189
if (!(algorithm instanceof weka.filters.Filter)) {
190
throw new IllegalArgumentException(algorithm.getClass()+" : incorrect "
191
+"type of algorithm (Filter)");
193
setFilter((weka.filters.Filter)algorithm);
197
* Get the filter wrapped by this bean
199
* @return an <code>Object</code> value
201
public Object getWrappedAlgorithm() {
206
* Accept a training set
208
* @param e a <code>TrainingSetEvent</code> value
210
public void acceptTrainingSet(TrainingSetEvent e) {
211
processTrainingOrDataSourceEvents(e);
214
private boolean m_structurePassedOn = false;
216
* Accept an instance for processing by StreamableFilters only
218
* @param e an <code>InstanceEvent</code> value
220
public void acceptInstance(InstanceEvent e) {
222
if (m_filterThread != null) {
223
String messg = "Filter is currently batch processing!";
225
m_log.logMessage(messg);
227
System.err.println(messg);
231
if (!(m_Filter instanceof StreamableFilter)) {
233
m_log.logMessage("ERROR : "+m_Filter.getClass().getName()
234
+"can't process streamed instances; can't continue");
238
if (e.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
240
//notifyInstanceListeners(e);
241
// Instances dataset = e.getInstance().dataset();
242
Instances dataset = e.getStructure();
243
if (m_Filter instanceof SupervisedFilter) {
244
// defualt to last column if no class is set
245
if (dataset.classIndex() < 0) {
246
dataset.setClassIndex(dataset.numAttributes()-1);
250
m_Filter.setInputFormat(dataset);
251
// attempt to determine post-filtering
252
// structure. If successful this can be passed on to instance
253
// listeners as a new FORMAT_AVAILABLE event.
254
m_structurePassedOn = false;
256
if (m_Filter.isOutputFormatDefined()) {
257
// System.err.println(m_Filter.getOutputFormat());
258
m_ie.setStructure(m_Filter.getOutputFormat());
259
notifyInstanceListeners(m_ie);
260
m_structurePassedOn = true;
262
} catch (Exception ex) {
263
System.err.println("Error in obtaining post-filter structure: Filter.java");
265
} catch (Exception ex) {
266
ex.printStackTrace();
271
// pass instance through the filter
273
if (!m_Filter.input(e.getInstance())) {
275
m_log.logMessage("ERROR : filter not ready to output instance");
280
// collect output instance.
281
Instance filteredInstance = m_Filter.output();
282
if (filteredInstance == null) {
285
if (!m_structurePassedOn) {
286
// pass on the new structure first
287
m_ie.setStructure(new Instances(filteredInstance.dataset(), 0));
288
notifyInstanceListeners(m_ie);
289
m_structurePassedOn = true;
292
m_ie.setInstance(filteredInstance);
293
m_ie.setStatus(e.getStatus());
294
notifyInstanceListeners(m_ie);
295
} catch (Exception ex) {
297
m_log.logMessage(ex.toString());
299
ex.printStackTrace();
303
private void processTrainingOrDataSourceEvents(final EventObject e) {
304
boolean structureOnly = false;
305
if (e instanceof DataSetEvent) {
306
structureOnly = ((DataSetEvent)e).isStructureOnly();
308
notifyDataOrTrainingListeners(e);
311
if (e instanceof TrainingSetEvent) {
312
structureOnly = ((TrainingSetEvent)e).isStructureOnly();
314
notifyDataOrTrainingListeners(e);
317
if (structureOnly && !(m_Filter instanceof StreamableFilter)) {
318
return; // nothing can be done
321
if (m_filterThread == null) {
323
if (m_state == IDLE) {
324
synchronized (this) {
325
m_state = FILTERING_TRAINING;
327
m_trainingSet = (e instanceof TrainingSetEvent)
328
? ((TrainingSetEvent)e).getTrainingSet()
329
: ((DataSetEvent)e).getDataSet();
331
final String oldText = m_visual.getText();
332
m_filterThread = new Thread() {
335
if (m_trainingSet != null) {
336
m_visual.setAnimated();
337
m_visual.setText("Filtering training data...");
339
m_log.statusMessage("Filter : filtering training data ("
340
+m_trainingSet.relationName());
342
m_Filter.setInputFormat(m_trainingSet);
343
Instances filteredData =
344
weka.filters.Filter.useFilter(m_trainingSet, m_Filter);
345
m_visual.setText(oldText);
346
m_visual.setStatic();
348
if (e instanceof TrainingSetEvent) {
349
ne = new TrainingSetEvent(weka.gui.beans.Filter.this,
351
((TrainingSetEvent)ne).m_setNumber =
352
((TrainingSetEvent)e).m_setNumber;
353
((TrainingSetEvent)ne).m_maxSetNumber =
354
((TrainingSetEvent)e).m_maxSetNumber;
356
ne = new DataSetEvent(weka.gui.beans.Filter.this,
360
notifyDataOrTrainingListeners(ne);
362
} catch (Exception ex) {
363
ex.printStackTrace();
365
m_visual.setText(oldText);
366
m_visual.setStatic();
368
if (isInterrupted()) {
369
m_trainingSet = null;
371
m_log.logMessage("Filter training set interrupted!");
372
m_log.statusMessage("OK");
379
m_filterThread.setPriority(Thread.MIN_PRIORITY);
380
m_filterThread.start();
382
m_filterThread = null;
385
} catch (Exception ex) {
386
ex.printStackTrace();
394
* @param e a <code>TestSetEvent</code> value
396
public void acceptTestSet(final TestSetEvent e) {
397
if(e.isStructureOnly())
398
notifyTestListeners(e);
399
if (m_trainingSet != null &&
400
m_trainingSet.equalHeaders(e.getTestSet()) &&
401
m_filterThread == null) {
403
if (m_state == IDLE) {
404
m_state = FILTERING_TEST;
406
m_testingSet = e.getTestSet();
407
final String oldText = m_visual.getText();
408
m_filterThread = new Thread() {
411
if (m_testingSet != null) {
412
m_visual.setAnimated();
413
m_visual.setText("Filtering test data...");
415
m_log.statusMessage("Filter : filtering test data ("
416
+m_testingSet.relationName());
418
Instances filteredTest =
419
weka.filters.Filter.useFilter(m_testingSet, m_Filter);
420
m_visual.setText(oldText);
421
m_visual.setStatic();
423
new TestSetEvent(weka.gui.beans.Filter.this,
425
ne.m_setNumber = e.m_setNumber;
426
ne.m_maxSetNumber = e.m_maxSetNumber;
427
notifyTestListeners(ne);
429
} catch (Exception ex) {
430
ex.printStackTrace();
432
m_visual.setText(oldText);
433
m_visual.setStatic();
435
if (isInterrupted()) {
436
m_trainingSet = null;
438
m_log.logMessage("Filter test set interrupted!");
439
m_log.statusMessage("OK");
446
m_filterThread.setPriority(Thread.MIN_PRIORITY);
447
m_filterThread.start();
449
m_filterThread = null;
451
} catch (Exception ex) {
452
ex.printStackTrace();
460
* @param e a <code>DataSetEvent</code> value
462
public void acceptDataSet(DataSetEvent e) {
463
processTrainingOrDataSourceEvents(e);
467
* Set the visual appearance of this bean
469
* @param newVisual a <code>BeanVisual</code> value
471
public void setVisual(BeanVisual newVisual) {
472
m_visual = newVisual;
476
* Get the visual appearance of this bean
478
* @return a <code>BeanVisual</code> value
480
public BeanVisual getVisual() {
485
* Use the default visual appearance
487
public void useDefaultVisual() {
488
m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultFilter.gif",
489
BeanVisual.ICON_PATH+"DefaultFilter_animated.gif");
493
* Add a training set listener
495
* @param tsl a <code>TrainingSetListener</code> value
497
public synchronized void addTrainingSetListener(TrainingSetListener tsl) {
498
m_trainingListeners.addElement(tsl);
502
* Remove a training set listener
504
* @param tsl a <code>TrainingSetListener</code> value
506
public synchronized void removeTrainingSetListener(TrainingSetListener tsl) {
507
m_trainingListeners.removeElement(tsl);
511
* Add a test set listener
513
* @param tsl a <code>TestSetListener</code> value
515
public synchronized void addTestSetListener(TestSetListener tsl) {
516
m_testListeners.addElement(tsl);
520
* Remove a test set listener
522
* @param tsl a <code>TestSetListener</code> value
524
public synchronized void removeTestSetListener(TestSetListener tsl) {
525
m_testListeners.removeElement(tsl);
529
* Add a data source listener
531
* @param dsl a <code>DataSourceListener</code> value
533
public synchronized void addDataSourceListener(DataSourceListener dsl) {
534
m_dataListeners.addElement(dsl);
538
* Remove a data source listener
540
* @param dsl a <code>DataSourceListener</code> value
542
public synchronized void removeDataSourceListener(DataSourceListener dsl) {
543
m_dataListeners.remove(dsl);
547
* Add an instance listener
549
* @param tsl an <code>InstanceListener</code> value
551
public synchronized void addInstanceListener(InstanceListener tsl) {
552
m_instanceListeners.addElement(tsl);
556
* Remove an instance listener
558
* @param tsl an <code>InstanceListener</code> value
560
public synchronized void removeInstanceListener(InstanceListener tsl) {
561
m_instanceListeners.removeElement(tsl);
564
private void notifyDataOrTrainingListeners(EventObject ce) {
566
synchronized (this) {
567
l = (ce instanceof TrainingSetEvent)
568
? (Vector)m_trainingListeners.clone()
569
: (Vector)m_dataListeners.clone();
572
for(int i = 0; i < l.size(); i++) {
573
if (ce instanceof TrainingSetEvent) {
574
((TrainingSetListener)l.elementAt(i)).
575
acceptTrainingSet((TrainingSetEvent)ce);
577
((DataSourceListener)l.elementAt(i)).acceptDataSet((DataSetEvent)ce);
583
private void notifyTestListeners(TestSetEvent ce) {
585
synchronized (this) {
586
l = (Vector)m_testListeners.clone();
589
for(int i = 0; i < l.size(); i++) {
590
((TestSetListener)l.elementAt(i)).acceptTestSet(ce);
595
protected void notifyInstanceListeners(InstanceEvent tse) {
597
synchronized (this) {
598
l = (Vector)m_instanceListeners.clone();
601
for(int i = 0; i < l.size(); i++) {
602
// System.err.println("Notifying instance listeners "
604
((InstanceListener)l.elementAt(i)).acceptInstance(tse);
610
* Returns true if, at this time,
611
* the object will accept a connection with respect to the supplied
614
* @param eventName the event
615
* @return true if the object will accept a connection
617
public boolean connectionAllowed(String eventName) {
619
if (m_listenees.containsKey(eventName)) {
623
/* reject a test event if we don't have a training or data set event
624
if (eventName.compareTo("testSet") == 0) {
625
if (!m_listenees.containsKey("trainingSet") &&
626
!m_listenees.containsKey("dataSet")) {
631
// will need to reject train/test listener if we have a
632
// data source listener and vis versa
633
if (m_listenees.containsKey("dataSet") &&
634
(eventName.compareTo("trainingSet") == 0 ||
635
eventName.compareTo("testSet") == 0 ||
636
eventName.compareTo("instance") == 0)) {
640
if ((m_listenees.containsKey("trainingSet") ||
641
m_listenees.containsKey("testSet")) &&
642
(eventName.compareTo("dataSet") == 0 ||
643
eventName.compareTo("instance") == 0)) {
647
if (m_listenees.containsKey("instance") &&
648
(eventName.compareTo("trainingSet") == 0 ||
649
eventName.compareTo("testSet") == 0 ||
650
eventName.compareTo("dataSet") == 0)) {
654
// reject an instance event connection if our filter isn't
656
if (eventName.compareTo("instance") == 0 &&
657
!(m_Filter instanceof StreamableFilter)) {
664
* Returns true if, at this time,
665
* the object will accept a connection according to the supplied
668
* @param esd the EventSetDescriptor
669
* @return true if the object will accept a connection
671
public boolean connectionAllowed(EventSetDescriptor esd) {
672
return connectionAllowed(esd.getName());
676
* Notify this object that it has been registered as a listener with
677
* a source with respect to the supplied event name
680
* @param source the source with which this object has been registered as
683
public synchronized void connectionNotification(String eventName,
685
if (connectionAllowed(eventName)) {
686
m_listenees.put(eventName, source);
687
if (m_Filter instanceof ConnectionNotificationConsumer) {
688
((ConnectionNotificationConsumer) m_Filter).
689
connectionNotification(eventName, source);
695
* Notify this object that it has been deregistered as a listener with
696
* a source with respect to the supplied event name
698
* @param eventName the event
699
* @param source the source with which this object has been registered as
702
public synchronized void disconnectionNotification(String eventName,
704
if (m_Filter instanceof ConnectionNotificationConsumer) {
705
((ConnectionNotificationConsumer) m_Filter).
706
disconnectionNotification(eventName, source);
708
m_listenees.remove(eventName);
712
* Function used to stop code that calls acceptTrainingSet, acceptTestSet,
713
* or acceptDataSet. This is
714
* needed as filtering is performed inside a separate
715
* thread of execution.
717
* @param tf a <code>boolean</code> value
719
private synchronized void block(boolean tf) {
722
// only block if thread is still doing something useful!
723
if (m_filterThread.isAlive() && m_state != IDLE) {
726
} catch (InterruptedException ex) {
734
* Stop all action if possible
737
// tell all listenees (upstream beans) to stop
738
Enumeration en = m_listenees.keys();
739
while (en.hasMoreElements()) {
740
Object tempO = m_listenees.get(en.nextElement());
741
if (tempO instanceof BeanCommon) {
742
System.err.println("Listener is BeanCommon");
743
((BeanCommon)tempO).stop();
753
* @param logger a <code>Logger</code> value
755
public void setLog(Logger logger) {
758
if (m_Filter != null && m_Filter instanceof LogWriter) {
759
((LogWriter) m_Filter).setLog(m_log);
764
* Return an enumeration of user requests
766
* @return an <code>Enumeration</code> value
768
public Enumeration enumerateRequests() {
769
Vector newVector = new Vector(0);
770
if (m_filterThread != null) {
771
newVector.addElement("Stop");
773
return newVector.elements();
777
* Perform the named request
779
* @param request a <code>String</code> value
780
* @exception IllegalArgumentException if an error occurs
782
public void performRequest(String request) {
783
if (request.compareTo("Stop") == 0) {
786
throw new IllegalArgumentException(request
787
+ " not supported (Filter)");
792
* Returns true, if at the current time, the named event could
793
* be generated. Assumes that supplied event names are names of
794
* events that could be generated by this bean.
796
* @param eventName the name of the event in question
797
* @return true if the named event could be generated at this point in
800
public boolean eventGeneratable(String eventName) {
801
// can't generate the named even if we are not receiving it as an
803
if (!m_listenees.containsKey(eventName)) {
806
Object source = m_listenees.get(eventName);
807
if (source instanceof EventConstraints) {
808
if (!((EventConstraints)source).eventGeneratable(eventName)) {
812
if (eventName.compareTo("instance") == 0) {
813
if (!(m_Filter instanceof StreamableFilter)) {