~ubuntu-branches/ubuntu/trusty/weka/trusty-proposed

« back to all changes in this revision

Viewing changes to weka/gui/beans/Classifier.java

  • Committer: Bazaar Package Importer
  • Author(s): Soeren Sonnenburg
  • Date: 2008-02-24 09:18:45 UTC
  • Revision ID: james.westby@ubuntu.com-20080224091845-1l8zy6fm6xipbzsr
Tags: upstream-3.5.7+tut1
ImportĀ upstreamĀ versionĀ 3.5.7+tut1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
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.
 
6
 *
 
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.
 
11
 *
 
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.
 
15
 */
 
16
 
 
17
/*
 
18
 *    Classifier.java
 
19
 *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
 
20
 *
 
21
 */
 
22
 
 
23
package weka.gui.beans;
 
24
 
 
25
import weka.classifiers.rules.ZeroR;
 
26
import weka.core.Instances;
 
27
import weka.gui.Logger;
 
28
 
 
29
import java.awt.BorderLayout;
 
30
import java.beans.EventSetDescriptor;
 
31
import java.io.Serializable;
 
32
import java.util.Enumeration;
 
33
import java.util.Hashtable;
 
34
import java.util.Vector;
 
35
 
 
36
import javax.swing.JPanel;
 
37
 
 
38
/**
 
39
 * Bean that wraps around weka.classifiers
 
40
 *
 
41
 * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
 
42
 * @version $Revision: 1.25 $
 
43
 * @since 1.0
 
44
 * @see JPanel
 
45
 * @see BeanCommon
 
46
 * @see Visible
 
47
 * @see WekaWrapper
 
48
 * @see Serializable
 
49
 * @see UserRequestAcceptor
 
50
 * @see TrainingSetListener
 
51
 * @see TestSetListener
 
52
 */
 
53
public class Classifier
 
54
  extends JPanel
 
55
  implements BeanCommon, Visible, 
 
56
             WekaWrapper, EventConstraints,
 
57
             Serializable, UserRequestAcceptor,
 
58
             TrainingSetListener, TestSetListener,
 
59
             InstanceListener {
 
60
 
 
61
  /** for serialization */
 
62
  private static final long serialVersionUID = 659603893917736008L;
 
63
 
 
64
  protected BeanVisual m_visual = 
 
65
    new BeanVisual("Classifier",
 
66
                   BeanVisual.ICON_PATH+"DefaultClassifier.gif",
 
67
                   BeanVisual.ICON_PATH+"DefaultClassifier_animated.gif");
 
68
 
 
69
  private static int IDLE = 0;
 
70
  private static int BUILDING_MODEL = 1;
 
71
  private static int CLASSIFYING = 2;
 
72
 
 
73
  private int m_state = IDLE;
 
74
 
 
75
  private Thread m_buildThread = null;
 
76
 
 
77
  /**
 
78
   * Global info for the wrapped classifier (if it exists).
 
79
   */
 
80
  protected String m_globalInfo;
 
81
 
 
82
  /**
 
83
   * Objects talking to us
 
84
   */
 
85
  private Hashtable m_listenees = new Hashtable();
 
86
 
 
87
  /**
 
88
   * Objects listening for batch classifier events
 
89
   */
 
90
  private Vector m_batchClassifierListeners = new Vector();
 
91
 
 
92
  /**
 
93
   * Objects listening for incremental classifier events
 
94
   */
 
95
  private Vector m_incrementalClassifierListeners = new Vector();
 
96
 
 
97
  /**
 
98
   * Objects listening for graph events
 
99
   */
 
100
  private Vector m_graphListeners = new Vector();
 
101
 
 
102
  /**
 
103
   * Objects listening for text events
 
104
   */
 
105
  private Vector m_textListeners = new Vector();
 
106
 
 
107
  /**
 
108
   * Holds training instances for batch training. Not transient because
 
109
   * header is retained for validating any instance events that this
 
110
   * classifier might be asked to predict in the future.
 
111
   */
 
112
  private Instances m_trainingSet;
 
113
  private transient Instances m_testingSet;
 
114
  private weka.classifiers.Classifier m_Classifier = new ZeroR();
 
115
  private IncrementalClassifierEvent m_ie = 
 
116
    new IncrementalClassifierEvent(this);
 
117
 
 
118
  /**
 
119
   * If the classifier is an incremental classifier, should we
 
120
   * update it (ie train it on incoming instances). This makes it
 
121
   * possible incrementally test on a separate stream of instances
 
122
   * without updating the classifier, or mix batch training/testing
 
123
   * with incremental training/testing
 
124
   */
 
125
  private boolean m_updateIncrementalClassifier = true;
 
126
 
 
127
  private transient Logger m_log = null;
 
128
 
 
129
  /**
 
130
   * Event to handle when processing incremental updates
 
131
   */
 
132
  private InstanceEvent m_incrementalEvent;
 
133
  private Double m_dummy = new Double(0.0);
 
134
 
 
135
  /**
 
136
   * Global info (if it exists) for the wrapped classifier
 
137
   *
 
138
   * @return the global info
 
139
   */
 
140
  public String globalInfo() {
 
141
    return m_globalInfo;
 
142
  }
 
143
 
 
144
  /**
 
145
   * Creates a new <code>Classifier</code> instance.
 
146
   */
 
147
  public Classifier() {
 
148
    setLayout(new BorderLayout());
 
149
    add(m_visual, BorderLayout.CENTER);
 
150
    setClassifier(m_Classifier);
 
151
  }
 
152
 
 
153
  /**
 
154
   * Set the classifier for this wrapper
 
155
   *
 
156
   * @param c a <code>weka.classifiers.Classifier</code> value
 
157
   */
 
158
  public void setClassifier(weka.classifiers.Classifier c) {
 
159
    boolean loadImages = true;
 
160
    if (c.getClass().getName().
 
161
        compareTo(m_Classifier.getClass().getName()) == 0) {
 
162
      loadImages = false;
 
163
    } else {
 
164
      // classifier has changed so any batch training status is now
 
165
      // invalid
 
166
      m_trainingSet = null;
 
167
    }
 
168
    m_Classifier = c;
 
169
    String classifierName = c.getClass().toString();
 
170
    classifierName = classifierName.substring(classifierName.
 
171
                                              lastIndexOf('.')+1, 
 
172
                                              classifierName.length());
 
173
    if (loadImages) {
 
174
      if (!m_visual.loadIcons(BeanVisual.ICON_PATH+classifierName+".gif",
 
175
                       BeanVisual.ICON_PATH+classifierName+"_animated.gif")) {
 
176
        useDefaultVisual();
 
177
      }
 
178
    }
 
179
    m_visual.setText(classifierName);
 
180
 
 
181
    if (!(m_Classifier instanceof weka.classifiers.UpdateableClassifier) &&
 
182
        (m_listenees.containsKey("instance"))) {
 
183
      if (m_log != null) {
 
184
        m_log.logMessage("WARNING : "+m_Classifier.getClass().getName()
 
185
                         +" is not an incremental classifier (Classifier)");
 
186
      }
 
187
    }
 
188
    // get global info
 
189
    m_globalInfo = KnowledgeFlowApp.getGlobalInfo(m_Classifier);
 
190
  }
 
191
 
 
192
  /**
 
193
   * Returns true if this classifier has an incoming connection that is
 
194
   * an instance stream
 
195
   *
 
196
   * @return true if has an incoming connection that is an instance stream
 
197
   */
 
198
  public boolean hasIncomingStreamInstances() {
 
199
    if (m_listenees.size() == 0) {
 
200
      return false;
 
201
    }
 
202
    if (m_listenees.containsKey("instance")) {
 
203
      return true;
 
204
    }
 
205
    return false;
 
206
  }
 
207
 
 
208
  /**
 
209
   * Returns true if this classifier has an incoming connection that is
 
210
   * a batch set of instances
 
211
   *
 
212
   * @return a <code>boolean</code> value
 
213
   */
 
214
  public boolean hasIncomingBatchInstances() {
 
215
    if (m_listenees.size() == 0) {
 
216
      return false;
 
217
    }
 
218
    if (m_listenees.containsKey("trainingSet") ||
 
219
        m_listenees.containsKey("testSet")) {
 
220
      return true;
 
221
    }
 
222
    return false;
 
223
  }
 
224
 
 
225
  /**
 
226
   * Get the classifier currently set for this wrapper
 
227
   *
 
228
   * @return a <code>weka.classifiers.Classifier</code> value
 
229
   */
 
230
  public weka.classifiers.Classifier getClassifier() {
 
231
    return m_Classifier;
 
232
  }
 
233
 
 
234
  /**
 
235
   * Sets the algorithm (classifier) for this bean
 
236
   *
 
237
   * @param algorithm an <code>Object</code> value
 
238
   * @exception IllegalArgumentException if an error occurs
 
239
   */
 
240
  public void setWrappedAlgorithm(Object algorithm) 
 
241
    {
 
242
 
 
243
    if (!(algorithm instanceof weka.classifiers.Classifier)) { 
 
244
      throw new IllegalArgumentException(algorithm.getClass()+" : incorrect "
 
245
                                         +"type of algorithm (Classifier)");
 
246
    }
 
247
    setClassifier((weka.classifiers.Classifier)algorithm);
 
248
  }
 
249
 
 
250
  /**
 
251
   * Returns the wrapped classifier
 
252
   *
 
253
   * @return an <code>Object</code> value
 
254
   */
 
255
  public Object getWrappedAlgorithm() {
 
256
    return getClassifier();
 
257
  }
 
258
 
 
259
  public boolean getUpdateIncrementalClassifier() {
 
260
    return m_updateIncrementalClassifier;
 
261
  }
 
262
 
 
263
  public void setUpdateIncrementalClassifier(boolean update) {
 
264
    m_updateIncrementalClassifier = update;
 
265
  }
 
266
 
 
267
//    public void acceptDataSet(DataSetEvent e) {
 
268
//      // will wrap up data in a TrainingSetEvent and call acceptTrainingSet
 
269
//      // then will do same for TestSetEvent
 
270
//      acceptTrainingSet(new TrainingSetEvent(e.getSource(), e.getDataSet()));
 
271
//    }
 
272
 
 
273
  /**
 
274
   * Accepts an instance for incremental processing.
 
275
   *
 
276
   * @param e an <code>InstanceEvent</code> value
 
277
   */
 
278
  public void acceptInstance(InstanceEvent e) {
 
279
    /*    if (m_buildThread == null) {
 
280
          System.err.println("Starting handler ");
 
281
          startIncrementalHandler();
 
282
          } */
 
283
    //    if (m_Classifier instanceof weka.classifiers.UpdateableClassifier) {
 
284
    /*      synchronized(m_dummy) {
 
285
            m_state = BUILDING_MODEL;
 
286
            m_incrementalEvent = e;
 
287
            m_dummy.notifyAll();
 
288
            }
 
289
            try {
 
290
            //    if (m_state == BUILDING_MODEL && m_buildThread != null) {
 
291
            block(true);
 
292
            //    }
 
293
            } catch (Exception ex) {
 
294
            return;
 
295
            } */
 
296
    m_incrementalEvent = e;
 
297
    handleIncrementalEvent();
 
298
    //    }
 
299
  }
 
300
 
 
301
  /**
 
302
   * Handles initializing and updating an incremental classifier
 
303
   */
 
304
  private void handleIncrementalEvent() {
 
305
    if (m_buildThread != null) {
 
306
      String messg = "Classifier is currently batch training!";
 
307
      if (m_log != null) {
 
308
        m_log.logMessage(messg);
 
309
      } else {
 
310
        System.err.println(messg);
 
311
      }
 
312
      return;
 
313
    }
 
314
 
 
315
    if (m_incrementalEvent.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
 
316
      //      Instances dataset = m_incrementalEvent.getInstance().dataset();
 
317
      Instances dataset = m_incrementalEvent.getStructure();
 
318
      // default to the last column if no class is set
 
319
      if (dataset.classIndex() < 0) {
 
320
        //      System.err.println("Classifier : setting class index...");
 
321
        dataset.setClassIndex(dataset.numAttributes()-1);
 
322
      }
 
323
      try {
 
324
        // initialize classifier if m_trainingSet is null
 
325
        // otherwise assume that classifier has been pre-trained in batch
 
326
        // mode, *if* headers match
 
327
        if (m_trainingSet == null || (!dataset.equalHeaders(m_trainingSet))) {
 
328
          if (!(m_Classifier instanceof 
 
329
                weka.classifiers.UpdateableClassifier)) {
 
330
            if (m_log != null) {
 
331
              String msg = (m_trainingSet == null)
 
332
                ? "ERROR : "+m_Classifier.getClass().getName()
 
333
                +" has not been batch "
 
334
                +"trained; can't process instance events."
 
335
                : "ERROR : instance event's structure is different from "
 
336
                +"the data that "
 
337
                + "was used to batch train this classifier; can't continue.";
 
338
              m_log.logMessage(msg);
 
339
            }
 
340
            return;
 
341
          }
 
342
          if (m_trainingSet != null && 
 
343
              (!dataset.equalHeaders(m_trainingSet))) {
 
344
            if (m_log != null) {
 
345
              m_log.logMessage("Warning : structure of instance events differ "
 
346
                               +"from data used in batch training this "
 
347
                               +"classifier. Resetting classifier...");
 
348
            }
 
349
            m_trainingSet = null;
 
350
          }
 
351
          if (m_trainingSet == null) {
 
352
            // initialize the classifier if it hasn't been trained yet
 
353
            m_trainingSet = new Instances(dataset, 0);
 
354
            m_Classifier.buildClassifier(m_trainingSet);
 
355
          }
 
356
        }
 
357
      } catch (Exception ex) {
 
358
        ex.printStackTrace();
 
359
      }
 
360
      // Notify incremental classifier listeners of new batch
 
361
      System.err.println("NOTIFYING NEW BATCH");
 
362
      m_ie.setStructure(dataset); 
 
363
      m_ie.setClassifier(m_Classifier);
 
364
 
 
365
      notifyIncrementalClassifierListeners(m_ie);
 
366
      return;
 
367
    } else {
 
368
      if (m_trainingSet == null) {
 
369
        // simply return. If the training set is still null after
 
370
        // the first instance then the classifier must not be updateable
 
371
        // and hasn't been previously batch trained - therefore we can't
 
372
        // do anything meaningful
 
373
        return;
 
374
      }
 
375
    }
 
376
 
 
377
    try {
 
378
      // test on this instance
 
379
      int status = IncrementalClassifierEvent.WITHIN_BATCH;
 
380
      /*      if (m_incrementalEvent.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
 
381
              status = IncrementalClassifierEvent.NEW_BATCH; */
 
382
      /* } else */ if (m_incrementalEvent.getStatus() ==
 
383
                       InstanceEvent.BATCH_FINISHED) {
 
384
        status = IncrementalClassifierEvent.BATCH_FINISHED;
 
385
      }
 
386
 
 
387
      m_ie.setStatus(status); m_ie.setClassifier(m_Classifier);
 
388
      m_ie.setCurrentInstance(m_incrementalEvent.getInstance());
 
389
 
 
390
      notifyIncrementalClassifierListeners(m_ie);
 
391
 
 
392
      // now update on this instance (if class is not missing and classifier
 
393
      // is updateable and user has specified that classifier is to be
 
394
      // updated)
 
395
      if (m_Classifier instanceof weka.classifiers.UpdateableClassifier &&
 
396
          m_updateIncrementalClassifier == true &&
 
397
          !(m_incrementalEvent.getInstance().
 
398
            isMissing(m_incrementalEvent.getInstance().
 
399
                      dataset().classIndex()))) {
 
400
        ((weka.classifiers.UpdateableClassifier)m_Classifier).
 
401
          updateClassifier(m_incrementalEvent.getInstance());
 
402
      }
 
403
      if (m_incrementalEvent.getStatus() == 
 
404
          InstanceEvent.BATCH_FINISHED) {
 
405
        if (m_textListeners.size() > 0) {
 
406
          String modelString = m_Classifier.toString();
 
407
          String titleString = m_Classifier.getClass().getName();
 
408
 
 
409
          titleString = titleString.
 
410
            substring(titleString.lastIndexOf('.') + 1,
 
411
                      titleString.length());
 
412
          modelString = "=== Classifier model ===\n\n" +
 
413
            "Scheme:   " +titleString+"\n" +
 
414
            "Relation: "  + m_trainingSet.relationName() + "\n\n"
 
415
            + modelString;
 
416
          titleString = "Model: " + titleString;
 
417
          TextEvent nt = new TextEvent(this,
 
418
                                       modelString,
 
419
                                       titleString);
 
420
          notifyTextListeners(nt);
 
421
        }
 
422
      }
 
423
    } catch (Exception ex) {
 
424
      if (m_log != null) {
 
425
        m_log.logMessage(ex.toString());
 
426
      }
 
427
      ex.printStackTrace();
 
428
    }
 
429
  }
 
430
 
 
431
  /**
 
432
   * Unused at present
 
433
   */
 
434
  private void startIncrementalHandler() {
 
435
    if (m_buildThread == null) {
 
436
      m_buildThread = new Thread() {
 
437
          public void run() {
 
438
            while (true) {
 
439
              synchronized(m_dummy) {
 
440
                try {
 
441
                  m_dummy.wait();
 
442
                } catch (InterruptedException ex) {
 
443
                  //              m_buildThread = null;
 
444
                  //              System.err.println("Here");
 
445
                  return;
 
446
                }
 
447
              }
 
448
              Classifier.this.handleIncrementalEvent();
 
449
              m_state = IDLE;
 
450
              block(false);
 
451
            }
 
452
          }
 
453
        };
 
454
      m_buildThread.setPriority(Thread.MIN_PRIORITY);
 
455
      m_buildThread.start();
 
456
      // give thread a chance to start
 
457
      try {
 
458
        Thread.sleep(500);
 
459
      } catch (InterruptedException ex) {
 
460
      }
 
461
    }
 
462
  }
 
463
 
 
464
  /**
 
465
   * Accepts a training set and builds batch classifier
 
466
   *
 
467
   * @param e a <code>TrainingSetEvent</code> value
 
468
   */
 
469
  public void acceptTrainingSet(final TrainingSetEvent e) {
 
470
    if (e.isStructureOnly()) {
 
471
      // no need to build a classifier, instead just generate a dummy
 
472
      // BatchClassifierEvent in order to pass on instance structure to
 
473
      // any listeners - eg. PredictionAppender can use it to determine
 
474
      // the final structure of instances with predictions appended
 
475
      BatchClassifierEvent ce = 
 
476
        new BatchClassifierEvent(this, m_Classifier, 
 
477
                                 new DataSetEvent(this, e.getTrainingSet()),
 
478
                                 new DataSetEvent(this, e.getTrainingSet()),
 
479
                                 e.getSetNumber(), e.getMaxSetNumber());
 
480
 
 
481
      notifyBatchClassifierListeners(ce);
 
482
      return;
 
483
    }
 
484
    if (m_buildThread == null) {
 
485
      try {
 
486
        if (m_state == IDLE) {
 
487
          synchronized (this) {
 
488
            m_state = BUILDING_MODEL;
 
489
          }
 
490
          m_trainingSet = e.getTrainingSet();
 
491
          final String oldText = m_visual.getText();
 
492
          m_buildThread = new Thread() {
 
493
              public void run() {
 
494
                try {
 
495
                  if (m_trainingSet != null) {
 
496
                    if (m_trainingSet.classIndex() < 0) {
 
497
                      // assume last column is the class
 
498
                      m_trainingSet.setClassIndex(m_trainingSet.numAttributes()-1);
 
499
                      if (m_log != null) {
 
500
                        m_log.logMessage("Classifier : assuming last "
 
501
                                         +"column is the class");
 
502
                      }
 
503
                    }
 
504
                    m_visual.setAnimated();
 
505
                    m_visual.setText("Building model...");
 
506
                    if (m_log != null) {
 
507
                      m_log.statusMessage("Classifier : building model...");
 
508
                    }
 
509
                    buildClassifier();
 
510
 
 
511
                    if (m_Classifier instanceof weka.core.Drawable && 
 
512
                        m_graphListeners.size() > 0) {
 
513
                      String grphString = 
 
514
                        ((weka.core.Drawable)m_Classifier).graph();
 
515
                      int grphType = ((weka.core.Drawable)m_Classifier).graphType();
 
516
                      String grphTitle = m_Classifier.getClass().getName();
 
517
                      grphTitle = grphTitle.substring(grphTitle.
 
518
                                                      lastIndexOf('.')+1, 
 
519
                                                      grphTitle.length());
 
520
                      grphTitle = "Set " + e.getSetNumber() + " ("
 
521
                        +e.getTrainingSet().relationName() + ") "
 
522
                        +grphTitle;
 
523
                      
 
524
                      GraphEvent ge = new GraphEvent(Classifier.this, 
 
525
                                                     grphString, 
 
526
                                                     grphTitle,
 
527
                                                     grphType);
 
528
                      notifyGraphListeners(ge);
 
529
                    }
 
530
 
 
531
                    if (m_textListeners.size() > 0) {
 
532
                      String modelString = m_Classifier.toString();
 
533
                      String titleString = m_Classifier.getClass().getName();
 
534
                      
 
535
                      titleString = titleString.
 
536
                        substring(titleString.lastIndexOf('.') + 1,
 
537
                                  titleString.length());
 
538
                      modelString = "=== Classifier model ===\n\n" +
 
539
                        "Scheme:   " +titleString+"\n" +
 
540
                        "Relation: "  + m_trainingSet.relationName() + 
 
541
                        ((e.getMaxSetNumber() > 1) 
 
542
                         ? "\nTraining Fold: "+e.getSetNumber()
 
543
                         :"")
 
544
                        + "\n\n"
 
545
                        + modelString;
 
546
                      titleString = "Model: " + titleString;
 
547
 
 
548
                      TextEvent nt = new TextEvent(Classifier.this,
 
549
                                                   modelString,
 
550
                                                   titleString);
 
551
                      notifyTextListeners(nt);
 
552
                    }
 
553
                  }
 
554
                } catch (Exception ex) {
 
555
                  ex.printStackTrace();
 
556
                } finally {
 
557
                  m_visual.setText(oldText);
 
558
                  m_visual.setStatic();
 
559
                  m_state = IDLE;
 
560
                  if (isInterrupted()) {
 
561
                    // prevent any classifier events from being fired
 
562
                    m_trainingSet = null;
 
563
                    if (m_log != null) {
 
564
                      m_log.logMessage("Build classifier interrupted!");
 
565
                      m_log.statusMessage("OK");
 
566
                    }
 
567
                  } else {
 
568
                    // save header
 
569
                    //m_trainingSet = new Instances(m_trainingSet, 0);
 
570
                  }
 
571
                  if (m_log != null) {
 
572
                    m_log.statusMessage("OK");
 
573
                  }
 
574
                  block(false);
 
575
                }
 
576
              } 
 
577
            };
 
578
          m_buildThread.setPriority(Thread.MIN_PRIORITY);
 
579
          m_buildThread.start();
 
580
          // make sure the thread is still running before we block
 
581
          //      if (m_buildThread.isAlive()) {
 
582
          block(true);
 
583
            //    }
 
584
          m_buildThread = null;
 
585
          m_state = IDLE;
 
586
        }
 
587
      } catch (Exception ex) {
 
588
        ex.printStackTrace();
 
589
      }
 
590
    }
 
591
  }
 
592
 
 
593
  /**
 
594
   * Accepts a test set for a batch trained classifier
 
595
   *
 
596
   * @param e a <code>TestSetEvent</code> value
 
597
   */
 
598
  public void acceptTestSet(TestSetEvent e) {
 
599
 
 
600
    if (m_trainingSet != null) {
 
601
      try {
 
602
        if (m_state == IDLE) {
 
603
          synchronized(this) {
 
604
            m_state = CLASSIFYING;
 
605
          }
 
606
 
 
607
          m_testingSet = e.getTestSet();
 
608
          if (m_testingSet != null) {
 
609
            if (m_testingSet.classIndex() < 0) {
 
610
              m_testingSet.setClassIndex(m_testingSet.numAttributes()-1);
 
611
            }
 
612
          }
 
613
          if (m_trainingSet.equalHeaders(m_testingSet)) {
 
614
 
 
615
            BatchClassifierEvent ce = 
 
616
              new BatchClassifierEvent(this, m_Classifier,                                     
 
617
                                       new DataSetEvent(this, m_trainingSet),
 
618
                                       new DataSetEvent(this, e.getTestSet()),
 
619
                                  e.getSetNumber(), e.getMaxSetNumber());
 
620
 
 
621
            //      System.err.println("Just before notify classifier listeners");
 
622
            notifyBatchClassifierListeners(ce);
 
623
            //      System.err.println("Just after notify classifier listeners");
 
624
          }
 
625
          m_state = IDLE;
 
626
        }
 
627
      } catch (Exception ex) {
 
628
        ex.printStackTrace();
 
629
      }
 
630
    }
 
631
  }
 
632
 
 
633
 
 
634
  private void buildClassifier() throws Exception {
 
635
    m_Classifier.buildClassifier(m_trainingSet);
 
636
  }
 
637
 
 
638
  /**
 
639
   * Sets the visual appearance of this wrapper bean
 
640
   *
 
641
   * @param newVisual a <code>BeanVisual</code> value
 
642
   */
 
643
  public void setVisual(BeanVisual newVisual) {
 
644
    m_visual = newVisual;
 
645
  }
 
646
 
 
647
  /**
 
648
   * Gets the visual appearance of this wrapper bean
 
649
   */
 
650
  public BeanVisual getVisual() {
 
651
    return m_visual;
 
652
  }
 
653
 
 
654
  /**
 
655
   * Use the default visual appearance for this bean
 
656
   */
 
657
  public void useDefaultVisual() {
 
658
    // try to get a default for this package of classifiers
 
659
    String name = m_Classifier.getClass().toString();
 
660
    String packageName = name.substring(0, name.lastIndexOf('.'));
 
661
    packageName = 
 
662
      packageName.substring(packageName.lastIndexOf('.')+1,
 
663
                            packageName.length());
 
664
    if (!m_visual.loadIcons(BeanVisual.ICON_PATH+"Default_"+packageName
 
665
                            +"Classifier.gif",
 
666
                            BeanVisual.ICON_PATH+"Default_"+packageName
 
667
                            +"Classifier_animated.gif")) {
 
668
      m_visual.loadIcons(BeanVisual.
 
669
                         ICON_PATH+"DefaultClassifier.gif",
 
670
                         BeanVisual.
 
671
                         ICON_PATH+"DefaultClassifier_animated.gif");
 
672
    }
 
673
  }
 
674
 
 
675
  /**
 
676
   * Add a batch classifier listener
 
677
   *
 
678
   * @param cl a <code>BatchClassifierListener</code> value
 
679
   */
 
680
  public synchronized void 
 
681
    addBatchClassifierListener(BatchClassifierListener cl) {
 
682
    m_batchClassifierListeners.addElement(cl);
 
683
  }
 
684
 
 
685
  /**
 
686
   * Remove a batch classifier listener
 
687
   *
 
688
   * @param cl a <code>BatchClassifierListener</code> value
 
689
   */
 
690
  public synchronized void 
 
691
    removeBatchClassifierListener(BatchClassifierListener cl) {
 
692
    m_batchClassifierListeners.remove(cl);
 
693
  }
 
694
 
 
695
  /**
 
696
   * Notify all batch classifier listeners of a batch classifier event
 
697
   *
 
698
   * @param ce a <code>BatchClassifierEvent</code> value
 
699
   */
 
700
  private void notifyBatchClassifierListeners(BatchClassifierEvent ce) {
 
701
    Vector l;
 
702
    synchronized (this) {
 
703
      l = (Vector)m_batchClassifierListeners.clone();
 
704
    }
 
705
    if (l.size() > 0) {
 
706
      for(int i = 0; i < l.size(); i++) {
 
707
        ((BatchClassifierListener)l.elementAt(i)).acceptClassifier(ce);
 
708
      }
 
709
    }
 
710
  }
 
711
 
 
712
  /**
 
713
   * Add a graph listener
 
714
   *
 
715
   * @param cl a <code>GraphListener</code> value
 
716
   */
 
717
  public synchronized void addGraphListener(GraphListener cl) {
 
718
    m_graphListeners.addElement(cl);
 
719
  }
 
720
 
 
721
  /**
 
722
   * Remove a graph listener
 
723
   *
 
724
   * @param cl a <code>GraphListener</code> value
 
725
   */
 
726
  public synchronized void removeGraphListener(GraphListener cl) {
 
727
    m_graphListeners.remove(cl);
 
728
  }
 
729
 
 
730
  /**
 
731
   * Notify all graph listeners of a graph event
 
732
   *
 
733
   * @param ge a <code>GraphEvent</code> value
 
734
   */
 
735
  private void notifyGraphListeners(GraphEvent ge) {
 
736
    Vector l;
 
737
    synchronized (this) {
 
738
      l = (Vector)m_graphListeners.clone();
 
739
    }
 
740
    if (l.size() > 0) {
 
741
      for(int i = 0; i < l.size(); i++) {
 
742
        ((GraphListener)l.elementAt(i)).acceptGraph(ge);
 
743
      }
 
744
    }
 
745
  }
 
746
 
 
747
  /**
 
748
   * Add a text listener
 
749
   *
 
750
   * @param cl a <code>TextListener</code> value
 
751
   */
 
752
  public synchronized void addTextListener(TextListener cl) {
 
753
    m_textListeners.addElement(cl);
 
754
  }
 
755
 
 
756
  /**
 
757
   * Remove a text listener
 
758
   *
 
759
   * @param cl a <code>TextListener</code> value
 
760
   */
 
761
  public synchronized void removeTextListener(TextListener cl) {
 
762
    m_textListeners.remove(cl);
 
763
  }
 
764
 
 
765
  /**
 
766
   * Notify all text listeners of a text event
 
767
   *
 
768
   * @param ge a <code>TextEvent</code> value
 
769
   */
 
770
  private void notifyTextListeners(TextEvent ge) {
 
771
    Vector l;
 
772
    synchronized (this) {
 
773
      l = (Vector)m_textListeners.clone();
 
774
    }
 
775
    if (l.size() > 0) {
 
776
      for(int i = 0; i < l.size(); i++) {
 
777
        ((TextListener)l.elementAt(i)).acceptText(ge);
 
778
      }
 
779
    }
 
780
  }
 
781
 
 
782
  /**
 
783
   * Add an incremental classifier listener
 
784
   *
 
785
   * @param cl an <code>IncrementalClassifierListener</code> value
 
786
   */
 
787
  public synchronized void 
 
788
    addIncrementalClassifierListener(IncrementalClassifierListener cl) {
 
789
    m_incrementalClassifierListeners.add(cl);
 
790
  }
 
791
 
 
792
  /**
 
793
   * Remove an incremental classifier listener
 
794
   *
 
795
   * @param cl an <code>IncrementalClassifierListener</code> value
 
796
   */
 
797
  public synchronized void 
 
798
    removeIncrementalClassifierListener(IncrementalClassifierListener cl) {
 
799
    m_incrementalClassifierListeners.remove(cl);
 
800
  }
 
801
 
 
802
  /**
 
803
   * Notify all incremental classifier listeners of an incremental classifier
 
804
   * event
 
805
   *
 
806
   * @param ce an <code>IncrementalClassifierEvent</code> value
 
807
   */
 
808
  private void 
 
809
    notifyIncrementalClassifierListeners(IncrementalClassifierEvent ce) {
 
810
    Vector l;
 
811
    synchronized (this) {
 
812
      l = (Vector)m_incrementalClassifierListeners.clone();
 
813
    }
 
814
    if (l.size() > 0) {
 
815
      for(int i = 0; i < l.size(); i++) {
 
816
        ((IncrementalClassifierListener)l.elementAt(i)).acceptClassifier(ce);
 
817
      }
 
818
    }
 
819
  }
 
820
 
 
821
  /**
 
822
   * Returns true if, at this time, 
 
823
   * the object will accept a connection with respect to the named event
 
824
   *
 
825
   * @param eventName the event
 
826
   * @return true if the object will accept a connection
 
827
   */
 
828
  public boolean connectionAllowed(String eventName) {
 
829
    /*    if (eventName.compareTo("instance") == 0) {
 
830
      if (!(m_Classifier instanceof weka.classifiers.UpdateableClassifier)) {
 
831
        return false;
 
832
      }
 
833
      } */
 
834
    if (m_listenees.containsKey(eventName)) {
 
835
      return false;
 
836
    }
 
837
    return true;
 
838
  }
 
839
 
 
840
  /**
 
841
   * Returns true if, at this time, 
 
842
   * the object will accept a connection according to the supplied
 
843
   * EventSetDescriptor
 
844
   *
 
845
   * @param esd the EventSetDescriptor
 
846
   * @return true if the object will accept a connection
 
847
   */
 
848
  public boolean connectionAllowed(EventSetDescriptor esd) {
 
849
    return connectionAllowed(esd.getName());
 
850
  }
 
851
 
 
852
  /**
 
853
   * Notify this object that it has been registered as a listener with
 
854
   * a source with respect to the named event
 
855
   *
 
856
   * @param eventName the event
 
857
   * @param source the source with which this object has been registered as
 
858
   * a listener
 
859
   */
 
860
  public synchronized void connectionNotification(String eventName,
 
861
                                                  Object source) {
 
862
    if (eventName.compareTo("instance") == 0) {
 
863
      if (!(m_Classifier instanceof weka.classifiers.UpdateableClassifier)) {
 
864
        if (m_log != null) {
 
865
          m_log.logMessage("Warning : " + m_Classifier.getClass().getName()
 
866
                           + " is not an updateable classifier. This "
 
867
                           +"classifier will only be evaluated on incoming "
 
868
                           +"instance events and not trained on them.");
 
869
        }
 
870
      }
 
871
    }
 
872
 
 
873
    if (connectionAllowed(eventName)) {
 
874
      m_listenees.put(eventName, source);
 
875
      /*      if (eventName.compareTo("instance") == 0) {
 
876
        startIncrementalHandler();
 
877
        } */
 
878
    }
 
879
  }
 
880
 
 
881
  /**
 
882
   * Notify this object that it has been deregistered as a listener with
 
883
   * a source with respect to the supplied event name
 
884
   *
 
885
   * @param eventName the event
 
886
   * @param source the source with which this object has been registered as
 
887
   * a listener
 
888
   */
 
889
  public synchronized void disconnectionNotification(String eventName,
 
890
                                                     Object source) {
 
891
    m_listenees.remove(eventName);
 
892
    if (eventName.compareTo("instance") == 0) {
 
893
      stop(); // kill the incremental handler thread if it is running
 
894
    }
 
895
  }
 
896
 
 
897
  /**
 
898
   * Function used to stop code that calls acceptTrainingSet. This is 
 
899
   * needed as classifier construction is performed inside a separate
 
900
   * thread of execution.
 
901
   *
 
902
   * @param tf a <code>boolean</code> value
 
903
   */
 
904
  private synchronized void block(boolean tf) {
 
905
 
 
906
    if (tf) {
 
907
      try {
 
908
          // only block if thread is still doing something useful!
 
909
        if (m_buildThread.isAlive() && m_state != IDLE) {
 
910
          wait();
 
911
          }
 
912
      } catch (InterruptedException ex) {
 
913
      }
 
914
    } else {
 
915
      notifyAll();
 
916
    }
 
917
  }
 
918
 
 
919
 
 
920
  /**
 
921
   * Stop any classifier action
 
922
   */
 
923
  public void stop() {
 
924
    // tell all listenees (upstream beans) to stop
 
925
    Enumeration en = m_listenees.keys();
 
926
    while (en.hasMoreElements()) {
 
927
      Object tempO = m_listenees.get(en.nextElement());
 
928
      if (tempO instanceof BeanCommon) {
 
929
        System.err.println("Listener is BeanCommon");
 
930
        ((BeanCommon)tempO).stop();
 
931
      }
 
932
    }
 
933
 
 
934
    // stop the build thread
 
935
    if (m_buildThread != null) {
 
936
      m_buildThread.interrupt();
 
937
      m_buildThread.stop();
 
938
      m_buildThread = null;
 
939
      m_visual.setStatic();
 
940
    }
 
941
  }
 
942
 
 
943
  /**
 
944
   * Set a logger
 
945
   *
 
946
   * @param logger a <code>Logger</code> value
 
947
   */
 
948
  public void setLog(Logger logger) {
 
949
    m_log = logger;
 
950
  }
 
951
 
 
952
  /**
 
953
   * Return an enumeration of requests that can be made by the user
 
954
   *
 
955
   * @return an <code>Enumeration</code> value
 
956
   */
 
957
  public Enumeration enumerateRequests() {
 
958
    Vector newVector = new Vector(0);
 
959
    if (m_buildThread != null) {
 
960
      newVector.addElement("Stop");
 
961
    }
 
962
    return newVector.elements();
 
963
  }
 
964
 
 
965
  /**
 
966
   * Perform a particular request
 
967
   *
 
968
   * @param request the request to perform
 
969
   * @exception IllegalArgumentException if an error occurs
 
970
   */
 
971
  public void performRequest(String request) {
 
972
    if (request.compareTo("Stop") == 0) {
 
973
      stop();
 
974
    } else {
 
975
      throw new IllegalArgumentException(request
 
976
                                         + " not supported (Classifier)");
 
977
    }
 
978
  }
 
979
 
 
980
  /**
 
981
   * Returns true, if at the current time, the event described by the
 
982
   * supplied event descriptor could be generated.
 
983
   *
 
984
   * @param esd an <code>EventSetDescriptor</code> value
 
985
   * @return a <code>boolean</code> value
 
986
   */
 
987
  public boolean eventGeneratable(EventSetDescriptor esd) {
 
988
    String eventName = esd.getName();
 
989
    return eventGeneratable(eventName);
 
990
  }
 
991
  
 
992
  /**
 
993
   * @param name of the event to check
 
994
   * @return true if eventName is one of the possible events
 
995
   * that this component can generate
 
996
   */
 
997
  private boolean generatableEvent(String eventName) {
 
998
    if (eventName.compareTo("graph") == 0
 
999
        || eventName.compareTo("text") == 0
 
1000
        || eventName.compareTo("batchClassifier") == 0
 
1001
        || eventName.compareTo("incrementalClassifier") == 0) {
 
1002
      return true;
 
1003
    }
 
1004
    return false;
 
1005
  }
 
1006
 
 
1007
  /**
 
1008
   * Returns true, if at the current time, the named event could
 
1009
   * be generated. Assumes that the supplied event name is
 
1010
   * an event that could be generated by this bean
 
1011
   *
 
1012
   * @param eventName the name of the event in question
 
1013
   * @return true if the named event could be generated at this point in
 
1014
   * time
 
1015
   */
 
1016
  public boolean eventGeneratable(String eventName) {
 
1017
    if (!generatableEvent(eventName)) {
 
1018
      return false;
 
1019
    }
 
1020
    if (eventName.compareTo("graph") == 0) {
 
1021
      // can't generate a GraphEvent if classifier is not drawable
 
1022
      if (!(m_Classifier instanceof weka.core.Drawable)) {
 
1023
        return false;
 
1024
      }
 
1025
      // need to have a training set before the classifier
 
1026
      // can generate a graph!
 
1027
      if (!m_listenees.containsKey("trainingSet")) {
 
1028
        return false;
 
1029
      }
 
1030
      // Source needs to be able to generate a trainingSet
 
1031
      // before we can generate a graph
 
1032
      Object source = m_listenees.get("trainingSet");
 
1033
       if (source instanceof EventConstraints) {
 
1034
        if (!((EventConstraints)source).eventGeneratable("trainingSet")) {
 
1035
          return false;
 
1036
        }
 
1037
      }
 
1038
    }
 
1039
 
 
1040
    if (eventName.compareTo("batchClassifier") == 0) {
 
1041
      if (!m_listenees.containsKey("testSet")) {
 
1042
        return false;
 
1043
      }
 
1044
      if (!m_listenees.containsKey("trainingSet") && 
 
1045
          m_trainingSet == null) {
 
1046
        return false;
 
1047
      }
 
1048
      Object source = m_listenees.get("testSet");
 
1049
      if (source instanceof EventConstraints) {
 
1050
        if (!((EventConstraints)source).eventGeneratable("testSet")) {
 
1051
          return false;
 
1052
        }
 
1053
      }
 
1054
      /*      source = m_listenees.get("trainingSet");
 
1055
      if (source instanceof EventConstraints) {
 
1056
        if (!((EventConstraints)source).eventGeneratable("trainingSet")) {
 
1057
          return false;
 
1058
        }
 
1059
        } */
 
1060
    }
 
1061
 
 
1062
    if (eventName.compareTo("text") == 0) {
 
1063
      if (!m_listenees.containsKey("trainingSet") &&
 
1064
          !m_listenees.containsKey("instance")) {
 
1065
        return false;
 
1066
      }
 
1067
      Object source = m_listenees.get("trainingSet");
 
1068
      if (source != null && source instanceof EventConstraints) {
 
1069
        if (!((EventConstraints)source).eventGeneratable("trainingSet")) {
 
1070
          return false;
 
1071
        }
 
1072
      }
 
1073
      source = m_listenees.get("instance");
 
1074
      if (source != null && source instanceof EventConstraints) {
 
1075
        if (!((EventConstraints)source).eventGeneratable("instance")) {
 
1076
          return false;
 
1077
        }
 
1078
      }
 
1079
    }
 
1080
 
 
1081
    if (eventName.compareTo("incrementalClassifier") == 0) {
 
1082
      /*      if (!(m_Classifier instanceof weka.classifiers.UpdateableClassifier)) {
 
1083
        return false;
 
1084
        } */
 
1085
      if (!m_listenees.containsKey("instance")) {
 
1086
        return false;
 
1087
      }
 
1088
      Object source = m_listenees.get("instance");
 
1089
      if (source instanceof EventConstraints) {
 
1090
        if (!((EventConstraints)source).eventGeneratable("instance")) {
 
1091
          return false;
 
1092
        }
 
1093
      }
 
1094
    }
 
1095
    return true;
 
1096
  }
 
1097
}