source: src/main/java/weka/gui/beans/Classifier.java @ 16

Last change on this file since 16 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 64.8 KB
Line 
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
23package weka.gui.beans;
24
25import java.awt.BorderLayout;
26import java.beans.EventSetDescriptor;
27import java.io.BufferedInputStream;
28import java.io.BufferedOutputStream;
29import java.io.File;
30import java.io.FileInputStream;
31import java.io.FileOutputStream;
32import java.io.ObjectInputStream;
33import java.io.ObjectOutputStream;
34import java.io.Serializable;
35import java.util.Date;
36import java.util.Enumeration;
37import java.util.Hashtable;
38import java.util.Vector;
39import java.util.concurrent.LinkedBlockingQueue;
40import java.util.concurrent.ThreadPoolExecutor;
41import java.util.concurrent.TimeUnit;
42
43import javax.swing.JFileChooser;
44import javax.swing.JOptionPane;
45import javax.swing.JPanel;
46import javax.swing.filechooser.FileFilter;
47
48import weka.classifiers.rules.ZeroR;
49import weka.core.Instances;
50import weka.core.OptionHandler;
51import weka.core.Utils;
52import weka.core.xml.KOML;
53import weka.core.xml.XStream;
54import weka.experiment.Task;
55import weka.experiment.TaskStatusInfo;
56import weka.gui.ExtensionFileFilter;
57import weka.gui.Logger;
58
59/**
60 * Bean that wraps around weka.classifiers
61 *
62 * @author <a href="mailto:mhall@cs.waikato.ac.nz">Mark Hall</a>
63 * @version $Revision: 6197 $
64 * @since 1.0
65 * @see JPanel
66 * @see BeanCommon
67 * @see Visible
68 * @see WekaWrapper
69 * @see Serializable
70 * @see UserRequestAcceptor
71 * @see TrainingSetListener
72 * @see TestSetListener
73 */
74public class Classifier
75  extends JPanel
76  implements BeanCommon, Visible, 
77             WekaWrapper, EventConstraints,
78             Serializable, UserRequestAcceptor,
79             TrainingSetListener, TestSetListener,
80             InstanceListener, ConfigurationProducer {
81
82  /** for serialization */
83  private static final long serialVersionUID = 659603893917736008L;
84
85  protected BeanVisual m_visual = 
86    new BeanVisual("Classifier",
87                   BeanVisual.ICON_PATH+"DefaultClassifier.gif",
88                   BeanVisual.ICON_PATH+"DefaultClassifier_animated.gif");
89
90  private static int IDLE = 0;
91  private static int BUILDING_MODEL = 1;
92  private static int CLASSIFYING = 2;
93
94  private int m_state = IDLE;
95
96  //private Thread m_buildThread = null;
97
98  /**
99   * Global info for the wrapped classifier (if it exists).
100   */
101  protected String m_globalInfo;
102
103  /**
104   * Objects talking to us
105   */
106  private Hashtable m_listenees = new Hashtable();
107
108  /**
109   * Objects listening for batch classifier events
110   */
111  private Vector m_batchClassifierListeners = new Vector();
112
113  /**
114   * Objects listening for incremental classifier events
115   */
116  private Vector m_incrementalClassifierListeners = new Vector();
117
118  /**
119   * Objects listening for graph events
120   */
121  private Vector m_graphListeners = new Vector();
122
123  /**
124   * Objects listening for text events
125   */
126  private Vector m_textListeners = new Vector();
127
128  /**
129   * Holds training instances for batch training. Not transient because
130   * header is retained for validating any instance events that this
131   * classifier might be asked to predict in the future.
132   */
133  private Instances m_trainingSet;
134  private transient Instances m_testingSet;
135  private weka.classifiers.Classifier m_Classifier = new ZeroR();
136  /** Template used for creating copies when building in parallel */
137  private weka.classifiers.Classifier m_ClassifierTemplate = m_Classifier;
138 
139  private IncrementalClassifierEvent m_ie = 
140    new IncrementalClassifierEvent(this);
141
142  /** the extension for serialized models (binary Java serialization) */
143  public final static String FILE_EXTENSION = "model";
144
145  private transient JFileChooser m_fileChooser = null; 
146
147  protected FileFilter m_binaryFilter =
148    new ExtensionFileFilter("."+FILE_EXTENSION, "Binary serialized model file (*"
149                            + FILE_EXTENSION + ")");
150
151  protected FileFilter m_KOMLFilter =
152    new ExtensionFileFilter(KOML.FILE_EXTENSION + FILE_EXTENSION,
153                            "XML serialized model file (*"
154                            + KOML.FILE_EXTENSION + FILE_EXTENSION + ")");
155
156  protected FileFilter m_XStreamFilter =
157    new ExtensionFileFilter(XStream.FILE_EXTENSION + FILE_EXTENSION,
158                            "XML serialized model file (*"
159                            + XStream.FILE_EXTENSION + FILE_EXTENSION + ")");
160
161  /**
162   * If the classifier is an incremental classifier, should we
163   * update it (ie train it on incoming instances). This makes it
164   * possible incrementally test on a separate stream of instances
165   * without updating the classifier, or mix batch training/testing
166   * with incremental training/testing
167   */
168  private boolean m_updateIncrementalClassifier = true;
169
170  private transient Logger m_log = null;
171
172  /**
173   * Event to handle when processing incremental updates
174   */
175  private InstanceEvent m_incrementalEvent;
176 
177  /**
178   * Number of threads to use to train models with
179   */
180  protected int m_executionSlots = 2;
181 
182//  protected int m_queueSize = 5;
183 
184  /**
185   * Pool of threads to train models on incoming data
186   */
187  protected transient ThreadPoolExecutor m_executorPool; 
188 
189  /**
190   * Stores completed models and associated data sets.
191   */
192  protected transient BatchClassifierEvent[][] m_outputQueues;
193
194  /**
195   * Stores which sets from which runs have been completed.
196   */
197  protected transient boolean[][] m_completedSets;
198 
199  /**
200   * Identifier for the current batch. A batch is a group
201   * of related runs/sets.
202   */
203  protected transient Date m_currentBatchIdentifier;
204 
205  /**
206   * Holds original icon label text
207   */
208  protected String m_oldText = "";
209 
210  /**
211   * true if we should reject any further training
212   * data sets, until all processing has been finished,
213   *  once we've received the last fold of
214   * the last run.
215   */
216  protected boolean m_reject = false;
217 
218  /**
219   * True if we should block rather reject until
220   * all processing has been completed.
221   */
222  protected boolean m_block = false;
223
224  /**
225   * Global info (if it exists) for the wrapped classifier
226   *
227   * @return the global info
228   */
229  public String globalInfo() {
230    return m_globalInfo;
231  }
232
233  /**
234   * Creates a new <code>Classifier</code> instance.
235   */
236  public Classifier() {
237    setLayout(new BorderLayout());
238    add(m_visual, BorderLayout.CENTER);
239    setClassifierTemplate(m_ClassifierTemplate);
240   
241    //setupFileChooser();
242  }
243 
244  private void startExecutorPool() {
245   
246    if (m_executorPool != null) {
247      m_executorPool.shutdownNow();
248    }
249   
250    m_executorPool = new ThreadPoolExecutor(m_executionSlots, m_executionSlots,
251        120, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
252  }
253
254  /**
255   * Set a custom (descriptive) name for this bean
256   *
257   * @param name the name to use
258   */
259  public void setCustomName(String name) {
260    m_visual.setText(name);
261  }
262
263  /**
264   * Get the custom (descriptive) name for this bean (if one has been set)
265   *
266   * @return the custom name (or the default name)
267   */
268  public String getCustomName() {
269    return m_visual.getText();
270  }
271
272  protected void setupFileChooser() {
273    if (m_fileChooser == null) {
274      m_fileChooser = 
275        new JFileChooser(new File(System.getProperty("user.dir")));
276    }
277
278    m_fileChooser.addChoosableFileFilter(m_binaryFilter);
279    if (KOML.isPresent()) {
280      m_fileChooser.addChoosableFileFilter(m_KOMLFilter);
281    }
282    if (XStream.isPresent()) {
283      m_fileChooser.addChoosableFileFilter(m_XStreamFilter);
284    }
285    m_fileChooser.setFileFilter(m_binaryFilter);
286  }
287 
288  /**
289   * Get the number of execution slots (threads) used
290   * to train models.
291   *
292   * @return the number of execution slots.
293   */
294  public int getExecutionSlots() {
295    return m_executionSlots;
296  }
297 
298  /**
299   * Set the number of execution slots (threads) to use to
300   * train models with.
301   *
302   * @param slots the number of execution slots to use.
303   */
304  public void setExecutionSlots(int slots) {
305    m_executionSlots = slots;
306  }
307 
308  /**
309   * Set whether to block on receiving the last fold
310   * of the last run rather than rejecting any further
311   * data until all processing is complete.
312   *
313   * @param block true if we should block on the
314   * last fold of the last run.
315   */
316  public void setBlockOnLastFold(boolean block) {
317    m_block = block;
318  }
319 
320  /**
321   * Gets whether we are blocking on the last fold of the
322   * last run rather than rejecting any further data until
323   * all processing has been completed.
324   *
325   * @return true if we are blocking on the last fold
326   * of the last run
327   */
328  public boolean getBlockOnLastFold() {
329    return m_block;
330  }
331
332  /**
333   * Set the template classifier for this wrapper
334   *
335   * @param c a <code>weka.classifiers.Classifier</code> value
336   */
337  public void setClassifierTemplate(weka.classifiers.Classifier c) {
338    boolean loadImages = true;
339    if (c.getClass().getName().
340        compareTo(m_ClassifierTemplate.getClass().getName()) == 0) {
341      loadImages = false;
342    } else {
343      // classifier has changed so any batch training status is now
344      // invalid
345      m_trainingSet = null;
346    }
347    m_ClassifierTemplate = c;
348    String classifierName = c.getClass().toString();
349    classifierName = classifierName.substring(classifierName.
350                                              lastIndexOf('.')+1, 
351                                              classifierName.length());
352    if (loadImages) {
353      if (!m_visual.loadIcons(BeanVisual.ICON_PATH+classifierName+".gif",
354                       BeanVisual.ICON_PATH+classifierName+"_animated.gif")) {
355        useDefaultVisual();
356      }
357    }
358    m_visual.setText(classifierName);
359
360    if (!(m_ClassifierTemplate instanceof weka.classifiers.UpdateableClassifier) &&
361        (m_listenees.containsKey("instance"))) {
362      if (m_log != null) {
363        m_log.logMessage("[Classifier] " + statusMessagePrefix() + " WARNING : "
364            + getCustomName() +" is not an incremental classifier");
365      }
366    }
367    // get global info
368    m_globalInfo = KnowledgeFlowApp.getGlobalInfo(m_ClassifierTemplate);
369  }
370 
371  /**
372   * Return the classifier template currently in use.
373   *
374   * @return the classifier template currently in use.
375   */
376  public weka.classifiers.Classifier getClassifierTemplate() {
377    return m_ClassifierTemplate;
378  }
379 
380  private void setTrainedClassifier(weka.classifiers.Classifier tc) {
381    m_Classifier = tc;
382   
383    // set the template
384    weka.classifiers.Classifier newTemplate = null;
385    try {
386      String[] options = ((OptionHandler)tc).getOptions();
387      newTemplate = weka.classifiers.AbstractClassifier.forName(tc.getClass().getName(), options);
388      setClassifierTemplate(newTemplate);
389    } catch (Exception ex) {
390      if (m_log != null) {
391        m_log.logMessage("[Classifier] " + statusMessagePrefix() + ex.getMessage());
392        String errorMessage = statusMessagePrefix()
393        + "ERROR: see log for details.";
394        m_log.statusMessage(errorMessage);       
395      } else {
396        ex.printStackTrace();
397      }
398    }   
399  }
400
401  /**
402   * Returns true if this classifier has an incoming connection that is
403   * an instance stream
404   *
405   * @return true if has an incoming connection that is an instance stream
406   */
407  public boolean hasIncomingStreamInstances() {
408    if (m_listenees.size() == 0) {
409      return false;
410    }
411    if (m_listenees.containsKey("instance")) {
412      return true;
413    }
414    return false;
415  }
416
417  /**
418   * Returns true if this classifier has an incoming connection that is
419   * a batch set of instances
420   *
421   * @return a <code>boolean</code> value
422   */
423  public boolean hasIncomingBatchInstances() {
424    if (m_listenees.size() == 0) {
425      return false;
426    }
427    if (m_listenees.containsKey("trainingSet") ||
428        m_listenees.containsKey("testSet")) {
429      return true;
430    }
431    return false;
432  }
433
434  /**
435   * Get the currently trained classifier.
436   *
437   * @return a <code>weka.classifiers.Classifier</code> value
438   */
439  public weka.classifiers.Classifier getClassifier() {
440    return m_Classifier;
441  }
442
443  /**
444   * Sets the algorithm (classifier) for this bean
445   *
446   * @param algorithm an <code>Object</code> value
447   * @exception IllegalArgumentException if an error occurs
448   */
449  public void setWrappedAlgorithm(Object algorithm) 
450    {
451
452    if (!(algorithm instanceof weka.classifiers.Classifier)) { 
453      throw new IllegalArgumentException(algorithm.getClass()+" : incorrect "
454                                         +"type of algorithm (Classifier)");
455    }
456    setClassifierTemplate((weka.classifiers.Classifier)algorithm);
457  }
458
459  /**
460   * Returns the wrapped classifier
461   *
462   * @return an <code>Object</code> value
463   */
464  public Object getWrappedAlgorithm() {
465    return getClassifierTemplate();
466  }
467
468  /**
469   * Get whether an incremental classifier will be updated on the
470   * incoming instance stream.
471   *
472   * @return true if an incremental classifier is to be updated.
473   */
474  public boolean getUpdateIncrementalClassifier() {
475    return m_updateIncrementalClassifier;
476  }
477
478  /**
479   * Set whether an incremental classifier will be updated on the
480   * incoming instance stream.
481   *
482   * @param update true if an incremental classifier is to be updated.
483   */
484  public void setUpdateIncrementalClassifier(boolean update) {
485    m_updateIncrementalClassifier = update;
486  }
487
488  /**
489   * Accepts an instance for incremental processing.
490   *
491   * @param e an <code>InstanceEvent</code> value
492   */
493  public void acceptInstance(InstanceEvent e) {
494    m_incrementalEvent = e;
495    handleIncrementalEvent();
496  }
497
498  /**
499   * Handles initializing and updating an incremental classifier
500   */
501  private void handleIncrementalEvent() {
502    if (m_executorPool != null && 
503        (m_executorPool.getQueue().size() > 0 || 
504            m_executorPool.getActiveCount() > 0)) {
505     
506      String messg = "[Classifier] " + statusMessagePrefix() 
507        + " is currently batch training!";
508      if (m_log != null) {
509        m_log.logMessage(messg);
510        m_log.statusMessage(statusMessagePrefix() + "WARNING: "
511            + "Can't accept instance - batch training in progress.");
512      } else {
513        System.err.println(messg);
514      }
515      return;
516    }
517
518    if (m_incrementalEvent.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
519      // clear any warnings/errors from the log
520      if (m_log != null) {
521        m_log.statusMessage(statusMessagePrefix() + "remove");
522      }
523     
524      //      Instances dataset = m_incrementalEvent.getInstance().dataset();
525      Instances dataset = m_incrementalEvent.getStructure();
526      // default to the last column if no class is set
527      if (dataset.classIndex() < 0) {
528        stop();
529        String errorMessage = statusMessagePrefix()
530            + "ERROR: no class attribute set in incoming stream!";
531        if (m_log != null) {
532          m_log.statusMessage(errorMessage);
533          m_log.logMessage("[" + getCustomName() + "] " + errorMessage);
534        } else {
535          System.err.println("[" + getCustomName() + "] " + errorMessage);
536        }
537        return;
538       
539        // System.err.println("Classifier : setting class index...");
540        //dataset.setClassIndex(dataset.numAttributes()-1);
541      }
542      try {
543        // initialize classifier if m_trainingSet is null
544        // otherwise assume that classifier has been pre-trained in batch
545        // mode, *if* headers match
546        if (m_trainingSet == null || !m_trainingSet.equalHeaders(dataset)) {
547          if (!(m_ClassifierTemplate instanceof 
548                weka.classifiers.UpdateableClassifier)) {
549            stop(); // stop all processing
550            if (m_log != null) {
551              String msg = (m_trainingSet == null)
552                ? statusMessagePrefix()
553                + "ERROR: classifier has not been batch "
554                +"trained; can't process instance events."
555                : statusMessagePrefix() 
556                  + "ERROR: instance event's structure is different from "
557                  +"the data that "
558                  + "was used to batch train this classifier; can't continue.";
559              m_log.logMessage("[Classifier] " + msg);
560              m_log.statusMessage(msg);
561            }
562            return;
563          }
564         
565          if (m_trainingSet != null && 
566              (!dataset.equalHeaders(m_trainingSet))) {
567            if (m_log != null) {
568              String msg = statusMessagePrefix() 
569              + " WARNING : structure of instance events differ "
570              +"from data used in batch training this "
571              +"classifier. Resetting classifier...";
572              m_log.logMessage("[Classifier] " + msg);
573              m_log.statusMessage(msg);
574            }
575            m_trainingSet = null;
576          }
577          if (m_trainingSet == null) {
578            // initialize the classifier if it hasn't been trained yet
579            m_trainingSet = new Instances(dataset, 0);
580            m_Classifier = weka.classifiers.AbstractClassifier.makeCopy(m_ClassifierTemplate);
581            m_Classifier.buildClassifier(m_trainingSet);
582          }
583        }
584      } catch (Exception ex) {
585        stop();
586        if (m_log != null) {
587          m_log.statusMessage(statusMessagePrefix()
588              + "ERROR (See log for details)");
589          m_log.logMessage("[Classifier] " + statusMessagePrefix()
590              + " problem during incremental processing. " 
591              + ex.getMessage());
592        }
593        ex.printStackTrace();
594      }
595      // Notify incremental classifier listeners of new batch
596      System.err.println("NOTIFYING NEW BATCH");
597      m_ie.setStructure(dataset); 
598      m_ie.setClassifier(m_Classifier);
599
600      notifyIncrementalClassifierListeners(m_ie);
601      return;
602    } else {
603      if (m_trainingSet == null) {
604        // simply return. If the training set is still null after
605        // the first instance then the classifier must not be updateable
606        // and hasn't been previously batch trained - therefore we can't
607        // do anything meaningful
608        return;
609      }
610    }
611
612    try {
613      // test on this instance
614      if (m_incrementalEvent.getInstance().dataset().classIndex() < 0) {
615        // System.err.println("Classifier : setting class index...");
616        m_incrementalEvent.getInstance().dataset().setClassIndex(
617            m_incrementalEvent.getInstance().dataset().numAttributes()-1);
618      }
619     
620      int status = IncrementalClassifierEvent.WITHIN_BATCH;
621      /*      if (m_incrementalEvent.getStatus() == InstanceEvent.FORMAT_AVAILABLE) {
622              status = IncrementalClassifierEvent.NEW_BATCH; */
623      /* } else */ if (m_incrementalEvent.getStatus() ==
624                       InstanceEvent.BATCH_FINISHED) {
625        status = IncrementalClassifierEvent.BATCH_FINISHED;
626      }
627
628      m_ie.setStatus(status); m_ie.setClassifier(m_Classifier);
629      m_ie.setCurrentInstance(m_incrementalEvent.getInstance());
630
631      notifyIncrementalClassifierListeners(m_ie);
632
633      // now update on this instance (if class is not missing and classifier
634      // is updateable and user has specified that classifier is to be
635      // updated)
636      if (m_ClassifierTemplate instanceof weka.classifiers.UpdateableClassifier &&
637          m_updateIncrementalClassifier == true &&
638          !(m_incrementalEvent.getInstance().
639            isMissing(m_incrementalEvent.getInstance().
640                      dataset().classIndex()))) {
641        ((weka.classifiers.UpdateableClassifier)m_Classifier).
642          updateClassifier(m_incrementalEvent.getInstance());
643      }
644      if (m_incrementalEvent.getStatus() == 
645          InstanceEvent.BATCH_FINISHED) {
646        if (m_textListeners.size() > 0) {
647          String modelString = m_Classifier.toString();
648          String titleString = m_Classifier.getClass().getName();
649
650          titleString = titleString.
651            substring(titleString.lastIndexOf('.') + 1,
652                      titleString.length());
653          modelString = "=== Classifier model ===\n\n" +
654            "Scheme:   " +titleString+"\n" +
655            "Relation: "  + m_trainingSet.relationName() + "\n\n"
656            + modelString;
657          titleString = "Model: " + titleString;
658          TextEvent nt = new TextEvent(this,
659                                       modelString,
660                                       titleString);
661          notifyTextListeners(nt);
662        }
663      }
664    } catch (Exception ex) {
665      stop();
666      if (m_log != null) {
667        m_log.logMessage("[Classifier] " + statusMessagePrefix()
668            + ex.getMessage());
669        m_log.statusMessage(statusMessagePrefix()
670            + "ERROR (see log for details)");
671        ex.printStackTrace();
672      } else {
673        ex.printStackTrace();
674      }
675    }
676  }
677 
678  protected class TrainingTask implements Runnable, Task {
679    private int m_runNum;
680    private int m_maxRunNum;
681    private int m_setNum;
682    private int m_maxSetNum;
683    private Instances m_train = null;
684    private TaskStatusInfo m_taskInfo = new TaskStatusInfo();
685
686    public TrainingTask(int runNum, int maxRunNum, 
687        int setNum, int maxSetNum, Instances train) {
688      m_runNum = runNum;
689      m_maxRunNum = maxRunNum;
690      m_setNum = setNum;
691      m_maxSetNum = maxSetNum;
692      m_train = train;
693      m_taskInfo.setExecutionStatus(TaskStatusInfo.TO_BE_RUN);
694    }
695
696    public void run() {
697      execute();
698    }
699
700    public void execute() {
701      try {
702        if (m_train != null) {
703          if (m_train.classIndex() < 0) {
704            // stop all processing
705            stop();
706            String errorMessage = statusMessagePrefix()
707                + "ERROR: no class attribute set in test data!";
708            if (m_log != null) {
709              m_log.statusMessage(errorMessage);
710              m_log.logMessage("[Classifier] " + errorMessage);
711            } else {
712              System.err.println("[Classifier] " + errorMessage);
713            }
714            return;
715           
716            // assume last column is the class
717/*            m_train.setClassIndex(m_train.numAttributes()-1);
718            if (m_log != null) {
719              m_log.logMessage("[Classifier] " + statusMessagePrefix()
720                  + " : assuming last "
721                  +"column is the class");
722            } */
723          }
724          if (m_runNum == 1 && m_setNum == 1) {
725            // set this back to idle once the last fold
726            // of the last run has completed
727            m_state = BUILDING_MODEL; // global state
728           
729            // local status of this runnable
730            m_taskInfo.setExecutionStatus(TaskStatusInfo.PROCESSING);
731          }
732         
733          //m_visual.setAnimated();
734          //m_visual.setText("Building model...");
735          String msg = statusMessagePrefix()
736            + "Building model for run " + m_runNum + " fold " + m_setNum;
737          if (m_log != null) {
738            m_log.statusMessage(msg);
739          } else {
740            System.err.println(msg);
741          }
742          // buildClassifier();
743         
744          // copy the classifier configuration
745          weka.classifiers.Classifier classifierCopy = 
746            weka.classifiers.AbstractClassifier.makeCopy(m_ClassifierTemplate);
747         
748          // build this model
749          classifierCopy.buildClassifier(m_train);
750          if (m_runNum == m_maxRunNum && m_setNum == m_maxSetNum) {
751            // Save the last classifier (might be used later on for
752            // classifying further test sets.
753            m_Classifier = classifierCopy;
754            m_trainingSet = m_train;
755          }
756                             
757          //if (m_batchClassifierListeners.size() > 0) {
758            // notify anyone who might be interested in just the model
759            // and training set.
760            BatchClassifierEvent ce = 
761              new BatchClassifierEvent(Classifier.this, classifierCopy, 
762                  new DataSetEvent(this, m_train),
763                  null, // no test set (yet)
764                  m_setNum, m_maxSetNum);
765            ce.setGroupIdentifier(m_currentBatchIdentifier.getTime());
766            notifyBatchClassifierListeners(ce);
767                       
768            // store in the output queue (if we have incoming test set events)
769            ce = 
770              new BatchClassifierEvent(Classifier.this, classifierCopy, 
771                  new DataSetEvent(this, m_train),
772                  null, // no test set (yet)
773                  m_setNum, m_maxSetNum);
774            ce.setGroupIdentifier(m_currentBatchIdentifier.getTime());
775            classifierTrainingComplete(ce);
776          //}
777
778          if (classifierCopy instanceof weka.core.Drawable && 
779              m_graphListeners.size() > 0) {
780            String grphString = 
781              ((weka.core.Drawable)classifierCopy).graph();
782            int grphType = ((weka.core.Drawable)classifierCopy).graphType();
783            String grphTitle = classifierCopy.getClass().getName();
784            grphTitle = grphTitle.substring(grphTitle.
785                lastIndexOf('.')+1, 
786                grphTitle.length());
787            grphTitle = "Set " + m_setNum + " ("
788            + m_train.relationName() + ") "
789            + grphTitle;
790
791            GraphEvent ge = new GraphEvent(Classifier.this, 
792                grphString, 
793                grphTitle,
794                grphType);
795            notifyGraphListeners(ge);
796          }
797
798          if (m_textListeners.size() > 0) {
799            String modelString = classifierCopy.toString();
800            String titleString = classifierCopy.getClass().getName();
801
802            titleString = titleString.
803            substring(titleString.lastIndexOf('.') + 1,
804                titleString.length());
805            modelString = "=== Classifier model ===\n\n" +
806            "Scheme:   " +titleString+"\n" +
807            "Relation: "  + m_train.relationName() + 
808            ((m_maxSetNum > 1) 
809                ? "\nTraining Fold: " + m_setNum
810                    :"")
811                    + "\n\n"
812                    + modelString;
813            titleString = "Model: " + titleString;
814
815            TextEvent nt = new TextEvent(Classifier.this,
816                modelString,
817                titleString);
818            notifyTextListeners(nt);
819          }
820        }
821      } catch (Exception ex) {
822        ex.printStackTrace();
823        if (m_log != null) {
824          String titleString = "[Classifier] " + statusMessagePrefix();
825
826          titleString += " run " + m_runNum + " fold " + m_setNum
827          + " failed to complete.";
828          m_log.logMessage(titleString + " (build classifier). " 
829              + ex.getMessage());
830          m_log.statusMessage(statusMessagePrefix() 
831              + "ERROR (see log for details)");
832          ex.printStackTrace();
833        }
834        m_taskInfo.setExecutionStatus(TaskStatusInfo.FAILED);
835        // Stop all processing
836        stop();
837      } finally {
838        m_visual.setStatic();
839        if (m_log != null) {
840          m_log.statusMessage(statusMessagePrefix() + "Finished.");
841        }
842        m_state = IDLE;
843        if (Thread.currentThread().isInterrupted()) {
844          // prevent any classifier events from being fired
845          m_trainingSet = null;
846          if (m_log != null) {
847            String titleString = "[Classifier] " + statusMessagePrefix();                 
848         
849            m_log.logMessage(titleString + " ("
850               + " run " + m_runNum + " fold " + m_setNum + ") interrupted!");
851            m_log.statusMessage(statusMessagePrefix() + "INTERRUPTED");
852           
853            /* // are we the last active thread?
854            if (m_executorPool.getActiveCount() == 1) {
855              String msg = "[Classifier] " + statusMessagePrefix()
856              + " last classifier unblocking...";
857              System.err.println(msg + " (interrupted)");
858              m_log.logMessage(msg + " (interrupted)");
859//              m_log.statusMessage(statusMessagePrefix() + "finished.");
860              m_block = false;
861              m_state = IDLE;
862              block(false);
863            } */
864          }
865          /*System.err.println("Queue size: " + m_executorPool.getQueue().size() +
866              " Active count: " + m_executorPool.getActiveCount()); */
867        } /* else {
868          // check to see if we are the last active thread
869          if (m_executorPool == null ||
870              (m_executorPool.getQueue().size() == 0 &&
871                  m_executorPool.getActiveCount() == 1)) {
872
873            String msg = "[Classifier] " + statusMessagePrefix()
874            + " last classifier unblocking...";
875            System.err.println(msg);
876            if (m_log != null) {
877              m_log.logMessage(msg);
878            } else {
879              System.err.println(msg);
880            }
881            //m_visual.setText(m_oldText);
882
883            if (m_log != null) {
884              m_log.statusMessage(statusMessagePrefix() + "Finished.");
885            }
886            // m_outputQueues = null; // free memory
887            m_block = false;
888            block(false);
889          }
890        } */
891      }
892    }
893 
894    public TaskStatusInfo getTaskStatus() {
895      // TODO
896      return null;
897    }     
898  }     
899 
900  /**
901   * Accepts a training set and builds batch classifier
902   *
903   * @param e a <code>TrainingSetEvent</code> value
904   */
905  public void acceptTrainingSet(final TrainingSetEvent e) {
906   
907    if (e.isStructureOnly()) {
908      // no need to build a classifier, instead just generate a dummy
909      // BatchClassifierEvent in order to pass on instance structure to
910      // any listeners - eg. PredictionAppender can use it to determine
911      // the final structure of instances with predictions appended
912      BatchClassifierEvent ce = 
913        new BatchClassifierEvent(this, m_Classifier, 
914                                 new DataSetEvent(this, e.getTrainingSet()),
915                                 new DataSetEvent(this, e.getTrainingSet()),
916                                 e.getSetNumber(), e.getMaxSetNumber());
917
918      notifyBatchClassifierListeners(ce);
919      return;
920    }
921   
922    if (m_reject) {
923      //block(true);
924      if (m_log != null) {
925        m_log.statusMessage(statusMessagePrefix() + "BUSY. Can't accept data "
926            + "at this time.");
927        m_log.logMessage("[Classifier] " + statusMessagePrefix()
928            + " BUSY. Can't accept data at this time.");
929      }
930      return;
931    }
932   
933    // Do some initialization if this is the first set of the first run
934    if (e.getRunNumber() == 1 && e.getSetNumber() == 1) {
935//      m_oldText = m_visual.getText();
936      // store the training header
937      m_trainingSet = new Instances(e.getTrainingSet(), 0);
938      m_state = BUILDING_MODEL;
939     
940      String msg = "[Classifier] " + statusMessagePrefix() 
941        + " starting executor pool ("
942        + getExecutionSlots() + " slots)...";
943      if (m_log != null) {
944        m_log.logMessage(msg);
945      } else {
946        System.err.println(msg);
947      }
948      // start the execution pool
949      if (m_executorPool == null) {
950        startExecutorPool();
951      }
952           
953      // setup output queues
954      msg = "[Classifier] " + statusMessagePrefix() + " setup output queues.";
955      if (m_log != null) {
956        m_log.logMessage(msg);
957      } else {
958        System.err.println(msg);
959      }
960     
961      m_outputQueues = 
962        new BatchClassifierEvent[e.getMaxRunNumber()][e.getMaxSetNumber()];
963      m_completedSets = new boolean[e.getMaxRunNumber()][e.getMaxSetNumber()];
964      m_currentBatchIdentifier = new Date();
965    }
966   
967    // create a new task and schedule for execution
968    TrainingTask newTask = new TrainingTask(e.getRunNumber(), e.getMaxRunNumber(),
969        e.getSetNumber(), e.getMaxSetNumber(), e.getTrainingSet());
970    String msg = "[Classifier] " + statusMessagePrefix() + " scheduling run " 
971    + e.getRunNumber() +" fold " + e.getSetNumber() + " for execution...";
972    if (m_log != null) {
973      m_log.logMessage(msg);
974    } else {
975      System.err.println(msg);
976    }
977   
978    // delay just a little bit
979    /*try {
980      Thread.sleep(10);
981    } catch (Exception ex){} */
982    m_executorPool.execute(newTask);
983  }
984
985  /**
986   * Accepts a test set for a batch trained classifier
987   *
988   * @param e a <code>TestSetEvent</code> value
989   */   
990  public synchronized void acceptTestSet(TestSetEvent e) {
991    if (m_reject) {
992      if (m_log != null) {
993        m_log.statusMessage(statusMessagePrefix() + "BUSY. Can't accept data "
994            + "at this time.");
995        m_log.logMessage("[Classifier] " + statusMessagePrefix()
996            + " BUSY. Can't accept data at this time.");
997      }
998      return;
999    }
1000   
1001    Instances testSet = e.getTestSet();
1002    if (testSet != null) {
1003      if (testSet.classIndex() < 0) {
1004  //        testSet.setClassIndex(testSet.numAttributes() - 1);
1005        // stop all processing
1006        stop();
1007        String errorMessage = statusMessagePrefix()
1008            + "ERROR: no class attribute set in test data!";
1009        if (m_log != null) {
1010          m_log.statusMessage(errorMessage);
1011          m_log.logMessage("[Classifier] " + errorMessage);
1012        } else {
1013          System.err.println("[Classifier] " + errorMessage);
1014        }
1015        return;
1016      }
1017    }
1018   
1019    // If we just have a test set connection or
1020    // there is just one run involving one set (and we are not
1021    // currently building a model), then use the
1022    // last saved model
1023    if (m_Classifier != null && m_state == IDLE && 
1024        (!m_listenees.containsKey("trainingSet") || 
1025        (e.getMaxRunNumber() == 1 && e.getMaxSetNumber() == 1))) {
1026      // if this is structure only then just return at this point
1027      if (e.getTestSet() != null && e.isStructureOnly()) {
1028        return;
1029      }
1030     
1031      // check that we have a training set/header (if we don't,
1032      // then it means that no model has been loaded
1033      if (m_trainingSet == null) {
1034        stop();
1035        String errorMessage = statusMessagePrefix()
1036            + "ERROR: no trained/loaded classifier to use for prediction!";
1037        if (m_log != null) {
1038          m_log.statusMessage(errorMessage);
1039          m_log.logMessage("[Classifier] " + errorMessage);
1040        } else {
1041          System.err.println("[Classifier] " + errorMessage);
1042        }
1043        return;
1044      }
1045     
1046      testSet = e.getTestSet();
1047      if (e.getRunNumber() == 1 && e.getSetNumber() == 1) {
1048        m_currentBatchIdentifier = new Date();
1049      }
1050     
1051      if (testSet != null) {       
1052        if (m_trainingSet.equalHeaders(testSet)) {
1053          BatchClassifierEvent ce =
1054            new BatchClassifierEvent(this, m_Classifier,                                       
1055                new DataSetEvent(this, m_trainingSet),
1056                new DataSetEvent(this, e.getTestSet()),
1057           e.getRunNumber(), e.getMaxRunNumber(), 
1058           e.getSetNumber(), e.getMaxSetNumber());
1059          ce.setGroupIdentifier(m_currentBatchIdentifier.getTime());
1060         
1061          if (m_log != null && !e.isStructureOnly()) {
1062            m_log.statusMessage(statusMessagePrefix() + "Finished.");
1063          }
1064          notifyBatchClassifierListeners(ce);         
1065        } else {
1066          // if headers do not match check to see if it's
1067          // just the class that is different and that
1068          // all class values are missing
1069          if (testSet.numInstances() > 0) {
1070            if (testSet.classIndex() == m_trainingSet.classIndex() && 
1071                testSet.attributeStats(testSet.classIndex()).missingCount ==
1072                testSet.numInstances()) {
1073              // now check the other attributes against the training
1074              // structure
1075              boolean ok = true;
1076              for (int i = 0; i < testSet.numAttributes(); i++) {
1077                if (i != testSet.classIndex()) {
1078                  ok = testSet.attribute(i).equals(m_trainingSet.attribute(i));
1079                  if (!ok) {
1080                    break;
1081                  }
1082                }
1083              }
1084             
1085              if (ok) {
1086                BatchClassifierEvent ce =
1087                  new BatchClassifierEvent(this, m_Classifier,                                       
1088                      new DataSetEvent(this, m_trainingSet),
1089                      new DataSetEvent(this, e.getTestSet()),
1090                 e.getRunNumber(), e.getMaxRunNumber(), 
1091                 e.getSetNumber(), e.getMaxSetNumber());
1092                ce.setGroupIdentifier(m_currentBatchIdentifier.getTime());
1093               
1094                if (m_log != null && !e.isStructureOnly()) {
1095                  m_log.statusMessage(statusMessagePrefix() + "Finished.");
1096                }
1097                notifyBatchClassifierListeners(ce);
1098              } else {
1099                stop();
1100                String errorMessage = statusMessagePrefix()
1101                + "ERROR: structure of training and test sets is not compatible!";
1102                if (m_log != null) {
1103                  m_log.statusMessage(errorMessage);
1104                  m_log.logMessage("[Classifier] " + errorMessage);
1105                } else {
1106                  System.err.println("[Classifier] " + errorMessage);
1107                }
1108              }
1109            }
1110          }
1111        }
1112      }
1113    } else {
1114/*      System.err.println("[Classifier] accepting test set: run "
1115          + e.getRunNumber() + " fold " + e.getSetNumber()); */
1116     
1117      if (m_outputQueues[e.getRunNumber() - 1][e.getSetNumber() - 1] == null) {
1118        // store an event with a null model and training set (to be filled in later)
1119        m_outputQueues[e.getRunNumber() - 1][e.getSetNumber() - 1] =
1120          new BatchClassifierEvent(this, null, null, 
1121              new DataSetEvent(this, e.getTestSet()),
1122              e.getRunNumber(), e.getMaxRunNumber(),
1123              e.getSetNumber(), e.getMaxSetNumber());
1124        if (e.getRunNumber() == e.getMaxRunNumber() && 
1125            e.getSetNumber() == e.getMaxSetNumber()) {
1126         
1127          // block on the last fold of the last run
1128          /* System.err.println("[Classifier] blocking on last fold of last run...");
1129          block(true); */
1130          m_reject = true;
1131          if (m_block) {
1132            block(true);
1133          }
1134        }
1135      } else {
1136        // Otherwise, there is a model here waiting for a test set...
1137        m_outputQueues[e.getRunNumber() - 1][e.getSetNumber() - 1].
1138          setTestSet(new DataSetEvent(this, e.getTestSet()));
1139        checkCompletedRun(e.getRunNumber(), e.getMaxRunNumber(), e.getMaxSetNumber());
1140      }
1141    }
1142  }
1143 
1144  private synchronized void classifierTrainingComplete(BatchClassifierEvent ce) {
1145    // check the output queues if we have an incoming test set connection
1146    if (m_listenees.containsKey("testSet")) {
1147      String msg = "[Classifier] " + statusMessagePrefix() 
1148      + " storing model for run " + ce.getRunNumber() 
1149      + " fold " + ce.getSetNumber();
1150      if (m_log != null) {
1151        m_log.logMessage(msg);
1152      } else {
1153        System.err.println(msg);
1154      }
1155     
1156      if (m_outputQueues[ce.getRunNumber() - 1][ce.getSetNumber() - 1] == null) {
1157        // store the event - test data filled in later
1158        m_outputQueues[ce.getRunNumber() - 1][ce.getSetNumber() - 1] = ce;
1159      } else {
1160        // there is a test set here waiting for a model and training set
1161        m_outputQueues[ce.getRunNumber() - 1][ce.getSetNumber() - 1].
1162          setClassifier(ce.getClassifier());
1163        m_outputQueues[ce.getRunNumber() - 1][ce.getSetNumber() - 1].
1164        setTrainSet(ce.getTrainSet());
1165       
1166      }
1167      checkCompletedRun(ce.getRunNumber(), ce.getMaxRunNumber(), ce.getMaxSetNumber());
1168    }
1169  }
1170 
1171  private synchronized void checkCompletedRun(int runNum, int maxRunNum, int maxSets) {
1172    // look to see if there are any completed classifiers that we can pass
1173    // on for evaluation
1174    for (int i = 0; i < maxSets; i++) {
1175      if (m_outputQueues[runNum - 1][i] != null) {
1176        if (m_outputQueues[runNum - 1][i].getClassifier() != null &&
1177            m_outputQueues[runNum - 1][i].getTestSet() != null) {
1178          String msg = "[Classifier] " + statusMessagePrefix() 
1179          + " dispatching run/set " + runNum + "/" + (i+1) + " to listeners.";
1180          if (m_log != null) {
1181            m_log.logMessage(msg);
1182          } else {
1183            System.err.println(msg);
1184          }
1185         
1186          // dispatch this one
1187          m_outputQueues[runNum - 1][i].setGroupIdentifier(m_currentBatchIdentifier.getTime());
1188          notifyBatchClassifierListeners(m_outputQueues[runNum - 1][i]);
1189          // save memory
1190          m_outputQueues[runNum - 1][i] = null;
1191          // mark as done
1192          m_completedSets[runNum - 1][i] = true;
1193        }
1194      }
1195    }
1196   
1197    // scan for completion
1198    boolean done = true;
1199    for (int i = 0; i < maxRunNum; i++) {
1200      for (int j = 0; j < maxSets; j++) {
1201        if (!m_completedSets[i][j]) {
1202          done = false;
1203          break;
1204        }
1205      }
1206      if (!done) {
1207        break;
1208      }
1209    }
1210   
1211    if (done) {
1212      String msg = "[Classifier] " + statusMessagePrefix() 
1213      + " last classifier unblocking...";
1214
1215      if (m_log != null) {
1216        m_log.logMessage(msg);
1217      } else {
1218        System.err.println(msg);
1219      }
1220      //m_visual.setText(m_oldText);
1221
1222      if (m_log != null) {
1223        m_log.statusMessage(statusMessagePrefix() + "Finished.");
1224      }
1225      // m_outputQueues = null; // free memory
1226      m_reject = false;
1227      block(false);
1228      m_state = IDLE;
1229    }
1230  }
1231
1232  /*private synchronized void checkCompletedRun(int runNum, int maxRunNum, int  maxSets) {
1233    boolean runOK = true;
1234    for (int i = 0; i < maxSets; i++) {
1235      if (m_outputQueues[runNum - 1][i] == null) {
1236        runOK = false;
1237        break;
1238      } else if (m_outputQueues[runNum - 1][i].getClassifier() == null ||
1239          m_outputQueues[runNum - 1][i].getTestSet() == null) {
1240        runOK = false;
1241        break;       
1242      }
1243    }
1244   
1245    if (runOK) {
1246      String msg = "[Classifier] " + statusMessagePrefix()
1247        + " dispatching run " + runNum + " to listeners.";
1248      if (m_log != null) {
1249        m_log.logMessage(msg);
1250      } else {
1251        System.err.println(msg);
1252      }
1253      // dispatch this run to listeners
1254      for (int i = 0; i < maxSets; i++) {
1255        notifyBatchClassifierListeners(m_outputQueues[runNum - 1][i]);
1256        // save memory
1257        m_outputQueues[runNum - 1][i] = null;
1258      }
1259     
1260      if (runNum == maxRunNum) {
1261        // unblock
1262        msg = "[Classifier] " + statusMessagePrefix()
1263        + " last classifier unblocking...";
1264
1265        if (m_log != null) {
1266          m_log.logMessage(msg);
1267        } else {
1268          System.err.println(msg);
1269        }
1270        //m_visual.setText(m_oldText);
1271
1272        if (m_log != null) {
1273          m_log.statusMessage(statusMessagePrefix() + "Finished.");
1274        }
1275        // m_outputQueues = null; // free memory
1276        m_reject = false;
1277        block(false);
1278        m_state = IDLE;
1279      }
1280    }
1281  } */
1282
1283  /**
1284   * Sets the visual appearance of this wrapper bean
1285   *
1286   * @param newVisual a <code>BeanVisual</code> value
1287   */
1288  public void setVisual(BeanVisual newVisual) {
1289    m_visual = newVisual;
1290  }
1291
1292  /**
1293   * Gets the visual appearance of this wrapper bean
1294   */
1295  public BeanVisual getVisual() {
1296    return m_visual;
1297  }
1298
1299  /**
1300   * Use the default visual appearance for this bean
1301   */
1302  public void useDefaultVisual() {
1303    // try to get a default for this package of classifiers
1304    String name = m_ClassifierTemplate.getClass().toString();
1305    String packageName = name.substring(0, name.lastIndexOf('.'));
1306    packageName = 
1307      packageName.substring(packageName.lastIndexOf('.')+1,
1308                            packageName.length());
1309    if (!m_visual.loadIcons(BeanVisual.ICON_PATH+"Default_"+packageName
1310                            +"Classifier.gif",
1311                            BeanVisual.ICON_PATH+"Default_"+packageName
1312                            +"Classifier_animated.gif")) {
1313      m_visual.loadIcons(BeanVisual.
1314                         ICON_PATH+"DefaultClassifier.gif",
1315                         BeanVisual.
1316                         ICON_PATH+"DefaultClassifier_animated.gif");
1317    }
1318  }
1319
1320  /**
1321   * Add a batch classifier listener
1322   *
1323   * @param cl a <code>BatchClassifierListener</code> value
1324   */
1325  public synchronized void 
1326    addBatchClassifierListener(BatchClassifierListener cl) {
1327    m_batchClassifierListeners.addElement(cl);
1328  }
1329
1330  /**
1331   * Remove a batch classifier listener
1332   *
1333   * @param cl a <code>BatchClassifierListener</code> value
1334   */
1335  public synchronized void 
1336    removeBatchClassifierListener(BatchClassifierListener cl) {
1337    m_batchClassifierListeners.remove(cl);
1338  }
1339
1340  /**
1341   * Notify all batch classifier listeners of a batch classifier event
1342   *
1343   * @param ce a <code>BatchClassifierEvent</code> value
1344   */
1345  private synchronized void notifyBatchClassifierListeners(BatchClassifierEvent ce) {
1346    Vector l;
1347    synchronized (this) {
1348      l = (Vector)m_batchClassifierListeners.clone();
1349    }
1350    if (l.size() > 0) {
1351      for(int i = 0; i < l.size(); i++) {
1352        ((BatchClassifierListener)l.elementAt(i)).acceptClassifier(ce);
1353      }
1354    }
1355  }
1356
1357  /**
1358   * Add a graph listener
1359   *
1360   * @param cl a <code>GraphListener</code> value
1361   */
1362  public synchronized void addGraphListener(GraphListener cl) {
1363    m_graphListeners.addElement(cl);
1364  }
1365
1366  /**
1367   * Remove a graph listener
1368   *
1369   * @param cl a <code>GraphListener</code> value
1370   */
1371  public synchronized void removeGraphListener(GraphListener cl) {
1372    m_graphListeners.remove(cl);
1373  }
1374
1375  /**
1376   * Notify all graph listeners of a graph event
1377   *
1378   * @param ge a <code>GraphEvent</code> value
1379   */
1380  private void notifyGraphListeners(GraphEvent ge) {
1381    Vector l;
1382    synchronized (this) {
1383      l = (Vector)m_graphListeners.clone();
1384    }
1385    if (l.size() > 0) {
1386      for(int i = 0; i < l.size(); i++) {
1387        ((GraphListener)l.elementAt(i)).acceptGraph(ge);
1388      }
1389    }
1390  }
1391
1392  /**
1393   * Add a text listener
1394   *
1395   * @param cl a <code>TextListener</code> value
1396   */
1397  public synchronized void addTextListener(TextListener cl) {
1398    m_textListeners.addElement(cl);
1399  }
1400
1401  /**
1402   * Remove a text listener
1403   *
1404   * @param cl a <code>TextListener</code> value
1405   */
1406  public synchronized void removeTextListener(TextListener cl) {
1407    m_textListeners.remove(cl);
1408  }
1409 
1410  /**
1411   * We don't have to keep track of configuration listeners (see the
1412   * documentation for ConfigurationListener/ConfigurationEvent).
1413   *
1414   * @param cl a ConfigurationListener.
1415   */
1416  public synchronized void addConfigurationListener(ConfigurationListener cl) {
1417   
1418  }
1419 
1420  /**
1421   * We don't have to keep track of configuration listeners (see the
1422   * documentation for ConfigurationListener/ConfigurationEvent).
1423   *
1424   * @param cl a ConfigurationListener.
1425   */
1426  public synchronized void removeConfigurationListener(ConfigurationListener cl) {
1427   
1428  }
1429
1430  /**
1431   * Notify all text listeners of a text event
1432   *
1433   * @param ge a <code>TextEvent</code> value
1434   */
1435  private void notifyTextListeners(TextEvent ge) {
1436    Vector l;
1437    synchronized (this) {
1438      l = (Vector)m_textListeners.clone();
1439    }
1440    if (l.size() > 0) {
1441      for(int i = 0; i < l.size(); i++) {
1442        ((TextListener)l.elementAt(i)).acceptText(ge);
1443      }
1444    }
1445  }
1446
1447  /**
1448   * Add an incremental classifier listener
1449   *
1450   * @param cl an <code>IncrementalClassifierListener</code> value
1451   */
1452  public synchronized void 
1453    addIncrementalClassifierListener(IncrementalClassifierListener cl) {
1454    m_incrementalClassifierListeners.add(cl);
1455  }
1456
1457  /**
1458   * Remove an incremental classifier listener
1459   *
1460   * @param cl an <code>IncrementalClassifierListener</code> value
1461   */
1462  public synchronized void 
1463    removeIncrementalClassifierListener(IncrementalClassifierListener cl) {
1464    m_incrementalClassifierListeners.remove(cl);
1465  }
1466
1467  /**
1468   * Notify all incremental classifier listeners of an incremental classifier
1469   * event
1470   *
1471   * @param ce an <code>IncrementalClassifierEvent</code> value
1472   */
1473  private void 
1474    notifyIncrementalClassifierListeners(IncrementalClassifierEvent ce) {
1475    Vector l;
1476    synchronized (this) {
1477      l = (Vector)m_incrementalClassifierListeners.clone();
1478    }
1479    if (l.size() > 0) {
1480      for(int i = 0; i < l.size(); i++) {
1481        ((IncrementalClassifierListener)l.elementAt(i)).acceptClassifier(ce);
1482      }
1483    }
1484  }
1485
1486  /**
1487   * Returns true if, at this time,
1488   * the object will accept a connection with respect to the named event
1489   *
1490   * @param eventName the event
1491   * @return true if the object will accept a connection
1492   */
1493  public boolean connectionAllowed(String eventName) {
1494    /*    if (eventName.compareTo("instance") == 0) {
1495      if (!(m_Classifier instanceof weka.classifiers.UpdateableClassifier)) {
1496        return false;
1497      }
1498      } */
1499    if (m_listenees.containsKey(eventName)) {
1500      return false;
1501    }
1502    return true;
1503  }
1504
1505  /**
1506   * Returns true if, at this time,
1507   * the object will accept a connection according to the supplied
1508   * EventSetDescriptor
1509   *
1510   * @param esd the EventSetDescriptor
1511   * @return true if the object will accept a connection
1512   */
1513  public boolean connectionAllowed(EventSetDescriptor esd) {
1514    return connectionAllowed(esd.getName());
1515  }
1516
1517  /**
1518   * Notify this object that it has been registered as a listener with
1519   * a source with respect to the named event
1520   *
1521   * @param eventName the event
1522   * @param source the source with which this object has been registered as
1523   * a listener
1524   */
1525  public synchronized void connectionNotification(String eventName,
1526                                                  Object source) {
1527    if (eventName.compareTo("instance") == 0) {
1528      if (!(m_ClassifierTemplate instanceof weka.classifiers.UpdateableClassifier)) {
1529        if (m_log != null) {
1530          String msg = statusMessagePrefix() + "WARNING: "
1531          + m_ClassifierTemplate.getClass().getName() 
1532          + " Is not an updateable classifier. This "
1533          +"classifier will only be evaluated on incoming "
1534          +"instance events and not trained on them.";
1535          m_log.logMessage("[Classifier] " + msg);
1536          m_log.statusMessage(msg);
1537        }
1538      }
1539    }
1540
1541    if (connectionAllowed(eventName)) {
1542      m_listenees.put(eventName, source);
1543      /*      if (eventName.compareTo("instance") == 0) {
1544        startIncrementalHandler();
1545        } */
1546    }
1547  }
1548
1549  /**
1550   * Notify this object that it has been deregistered as a listener with
1551   * a source with respect to the supplied event name
1552   *
1553   * @param eventName the event
1554   * @param source the source with which this object has been registered as
1555   * a listener
1556   */
1557  public synchronized void disconnectionNotification(String eventName,
1558                                                     Object source) {
1559    m_listenees.remove(eventName);
1560    if (eventName.compareTo("instance") == 0) {
1561      stop(); // kill the incremental handler thread if it is running
1562    }
1563  }
1564
1565  /**
1566   * Function used to stop code that calls acceptTrainingSet. This is
1567   * needed as classifier construction is performed inside a separate
1568   * thread of execution.
1569   *
1570   * @param tf a <code>boolean</code> value
1571   */
1572  private synchronized void block(boolean tf) {
1573
1574    if (tf) {
1575      try {
1576          // only block if thread is still doing something useful!
1577//      if (m_state != IDLE) {
1578          wait();
1579          //}
1580      } catch (InterruptedException ex) {
1581      }
1582    } else {
1583      notifyAll();
1584    }
1585  }
1586
1587
1588  /**
1589   * Stop any classifier action
1590   */
1591  public void stop() {
1592    // tell all listenees (upstream beans) to stop
1593    Enumeration en = m_listenees.keys();
1594    while (en.hasMoreElements()) {
1595      Object tempO = m_listenees.get(en.nextElement());
1596      if (tempO instanceof BeanCommon) {
1597        ((BeanCommon)tempO).stop();
1598      }
1599    }
1600   
1601    // shutdown the executor pool and reclaim storage
1602    if (m_executorPool != null) {
1603      m_executorPool.shutdownNow();
1604      m_executorPool.purge();
1605      m_executorPool = null;
1606    }
1607    m_reject = false;
1608    block(false);
1609    m_visual.setStatic();
1610    if (m_oldText.length() > 0) {
1611      //m_visual.setText(m_oldText);
1612    }
1613
1614    // stop the build thread
1615    /*if (m_buildThread != null) {
1616      m_buildThread.interrupt();
1617      m_buildThread.stop();
1618      m_buildThread = null;
1619      m_visual.setStatic();
1620    } */
1621  }
1622
1623  public void loadModel() {
1624    try {
1625      if (m_fileChooser == null) {
1626        // i.e. after de-serialization
1627        setupFileChooser();
1628      }
1629      int returnVal = m_fileChooser.showOpenDialog(this);
1630      if (returnVal == JFileChooser.APPROVE_OPTION) {
1631        File loadFrom = m_fileChooser.getSelectedFile();
1632
1633        // add extension if necessary
1634        if (m_fileChooser.getFileFilter() == m_binaryFilter) {
1635          if (!loadFrom.getName().toLowerCase().endsWith("." + FILE_EXTENSION)) {
1636            loadFrom = new File(loadFrom.getParent(),
1637                                loadFrom.getName() + "." + FILE_EXTENSION);
1638          }
1639        } else if (m_fileChooser.getFileFilter() == m_KOMLFilter) {
1640          if (!loadFrom.getName().toLowerCase().endsWith(KOML.FILE_EXTENSION 
1641                                                         + FILE_EXTENSION)) {
1642            loadFrom = new File(loadFrom.getParent(),
1643                                loadFrom.getName() + KOML.FILE_EXTENSION 
1644                                + FILE_EXTENSION);
1645          }
1646        } else if (m_fileChooser.getFileFilter() == m_XStreamFilter) {
1647          if (!loadFrom.getName().toLowerCase().endsWith(XStream.FILE_EXTENSION 
1648                                                        + FILE_EXTENSION)) {
1649            loadFrom = new File(loadFrom.getParent(),
1650                                loadFrom.getName() + XStream.FILE_EXTENSION 
1651                                + FILE_EXTENSION);
1652          }
1653        }
1654
1655        weka.classifiers.Classifier temp = null;
1656        Instances tempHeader = null;
1657        // KOML ?
1658        if ((KOML.isPresent()) &&
1659            (loadFrom.getAbsolutePath().toLowerCase().
1660             endsWith(KOML.FILE_EXTENSION + FILE_EXTENSION))) {
1661          Vector v = (Vector) KOML.read(loadFrom.getAbsolutePath());
1662          temp = (weka.classifiers.Classifier) v.elementAt(0);
1663          if (v.size() == 2) {
1664            // try and grab the header
1665            tempHeader = (Instances) v.elementAt(1);
1666          }
1667        } /* XStream */ else if ((XStream.isPresent()) &&
1668                                 (loadFrom.getAbsolutePath().toLowerCase().
1669                                  endsWith(XStream.FILE_EXTENSION + FILE_EXTENSION))) {
1670          Vector v = (Vector) XStream.read(loadFrom.getAbsolutePath());
1671          temp = (weka.classifiers.Classifier) v.elementAt(0);
1672          if (v.size() == 2) {
1673            // try and grab the header
1674            tempHeader = (Instances) v.elementAt(1);
1675          } 
1676        } /* binary */ else {
1677
1678          ObjectInputStream is = 
1679            new ObjectInputStream(new BufferedInputStream(
1680                                                          new FileInputStream(loadFrom)));
1681          // try and read the model
1682          temp = (weka.classifiers.Classifier)is.readObject();
1683          // try and read the header (if present)
1684          try {
1685            tempHeader = (Instances)is.readObject();
1686          } catch (Exception ex) {
1687            //            System.err.println("No header...");
1688            // quietly ignore
1689          }
1690          is.close();
1691        }
1692
1693        // Update name and icon
1694        setTrainedClassifier(temp);
1695        // restore header
1696        m_trainingSet = tempHeader;
1697
1698        if (m_log != null) {
1699          m_log.statusMessage(statusMessagePrefix() + "Loaded model.");
1700          m_log.logMessage("[Classifier] " + statusMessagePrefix() 
1701              + "Loaded classifier: "
1702              + m_Classifier.getClass().toString());
1703        }
1704      }
1705    } catch (Exception ex) {
1706      JOptionPane.showMessageDialog(Classifier.this,
1707                                    "Problem loading classifier.\n",
1708                                    "Load Model",
1709                                    JOptionPane.ERROR_MESSAGE);
1710      if (m_log != null) {
1711        m_log.statusMessage(statusMessagePrefix() + "ERROR: unable to load " +
1712                        "model (see log).");
1713        m_log.logMessage("[Classifier] " + statusMessagePrefix() 
1714            + "Problem loading classifier. " 
1715            + ex.getMessage());
1716      }
1717    }
1718  }
1719
1720  public void saveModel() {
1721    try {
1722      if (m_fileChooser == null) {
1723        // i.e. after de-serialization
1724        setupFileChooser();
1725      }
1726      int returnVal = m_fileChooser.showSaveDialog(this);
1727      if (returnVal == JFileChooser.APPROVE_OPTION) {
1728        File saveTo = m_fileChooser.getSelectedFile();
1729        String fn = saveTo.getAbsolutePath();
1730        if (m_fileChooser.getFileFilter() == m_binaryFilter) {
1731          if (!fn.toLowerCase().endsWith("." + FILE_EXTENSION)) {
1732            fn += "." + FILE_EXTENSION;
1733          }
1734        } else if (m_fileChooser.getFileFilter() == m_KOMLFilter) {
1735          if (!fn.toLowerCase().endsWith(KOML.FILE_EXTENSION + FILE_EXTENSION)) {
1736            fn += KOML.FILE_EXTENSION + FILE_EXTENSION;
1737          }
1738        } else if (m_fileChooser.getFileFilter() == m_XStreamFilter) {
1739          if (!fn.toLowerCase().endsWith(XStream.FILE_EXTENSION + FILE_EXTENSION)) {
1740            fn += XStream.FILE_EXTENSION + FILE_EXTENSION;
1741          }
1742        }
1743        saveTo = new File(fn);
1744
1745        // now serialize model
1746        // KOML?
1747        if ((KOML.isPresent()) &&
1748            saveTo.getAbsolutePath().toLowerCase().
1749            endsWith(KOML.FILE_EXTENSION + FILE_EXTENSION)) {
1750          SerializedModelSaver.saveKOML(saveTo,
1751                                        m_Classifier,
1752                                        (m_trainingSet != null)
1753                                        ? new Instances(m_trainingSet, 0)
1754                                        : null);
1755          /*          Vector v = new Vector();
1756          v.add(m_Classifier);
1757          if (m_trainingSet != null) {
1758            v.add(new Instances(m_trainingSet, 0));
1759          }
1760          v.trimToSize();
1761          KOML.write(saveTo.getAbsolutePath(), v); */
1762        } /* XStream */ else if ((XStream.isPresent()) &&
1763                                 saveTo.getAbsolutePath().toLowerCase().
1764            endsWith(XStream.FILE_EXTENSION + FILE_EXTENSION)) {
1765
1766          SerializedModelSaver.saveXStream(saveTo,
1767                                           m_Classifier,
1768                                           (m_trainingSet != null)
1769                                           ? new Instances(m_trainingSet, 0)
1770                                           : null);
1771          /*          Vector v = new Vector();
1772          v.add(m_Classifier);
1773          if (m_trainingSet != null) {
1774            v.add(new Instances(m_trainingSet, 0));
1775          }
1776          v.trimToSize();
1777          XStream.write(saveTo.getAbsolutePath(), v); */
1778        } else /* binary */ {
1779          ObjectOutputStream os = 
1780            new ObjectOutputStream(new BufferedOutputStream(
1781                                   new FileOutputStream(saveTo)));
1782          os.writeObject(m_Classifier);
1783          if (m_trainingSet != null) {
1784            Instances header = new Instances(m_trainingSet, 0);
1785            os.writeObject(header);
1786          }
1787          os.close();
1788        }
1789        if (m_log != null) {
1790          m_log.statusMessage(statusMessagePrefix() + "Model saved.");
1791          m_log.logMessage("[Classifier] " + statusMessagePrefix() 
1792              + " Saved classifier " + getCustomName());
1793        }
1794      }
1795    } catch (Exception ex) {
1796      JOptionPane.showMessageDialog(Classifier.this,
1797                                    "Problem saving classifier.\n",
1798                                    "Save Model",
1799                                    JOptionPane.ERROR_MESSAGE);
1800      if (m_log != null) {
1801        m_log.statusMessage(statusMessagePrefix() + "ERROR: unable to" +
1802                        " save model (see log).");
1803        m_log.logMessage("[Classifier] " + statusMessagePrefix() 
1804            + " Problem saving classifier " + getCustomName() 
1805            + ex.getMessage());
1806      }
1807    }
1808  }
1809
1810  /**
1811   * Set a logger
1812   *
1813   * @param logger a <code>Logger</code> value
1814   */
1815  public void setLog(Logger logger) {
1816    m_log = logger;
1817  }
1818
1819  /**
1820   * Return an enumeration of requests that can be made by the user
1821   *
1822   * @return an <code>Enumeration</code> value
1823   */
1824  public Enumeration enumerateRequests() {
1825    Vector newVector = new Vector(0);
1826    if (m_executorPool != null && 
1827        (m_executorPool.getQueue().size() > 0 || 
1828            m_executorPool.getActiveCount() > 0)) {
1829      newVector.addElement("Stop");
1830    }
1831
1832    if ((m_executorPool == null || 
1833        (m_executorPool.getQueue().size() == 0 && 
1834            m_executorPool.getActiveCount() == 0)) && 
1835        m_Classifier != null) {
1836      newVector.addElement("Save model");
1837    }
1838
1839    if (m_executorPool == null || 
1840        (m_executorPool.getQueue().size() == 0 && 
1841            m_executorPool.getActiveCount() == 0)) {
1842      newVector.addElement("Load model");
1843    }
1844    return newVector.elements();
1845  }
1846
1847  /**
1848   * Perform a particular request
1849   *
1850   * @param request the request to perform
1851   * @exception IllegalArgumentException if an error occurs
1852   */
1853  public void performRequest(String request) {
1854    if (request.compareTo("Stop") == 0) {
1855      stop();
1856    } else if (request.compareTo("Save model") == 0) {
1857      saveModel();
1858    } else if (request.compareTo("Load model") == 0) {
1859      loadModel();
1860    } else {
1861      throw new IllegalArgumentException(request
1862                                         + " not supported (Classifier)");
1863    }
1864  }
1865
1866  /**
1867   * Returns true, if at the current time, the event described by the
1868   * supplied event descriptor could be generated.
1869   *
1870   * @param esd an <code>EventSetDescriptor</code> value
1871   * @return a <code>boolean</code> value
1872   */
1873  public boolean eventGeneratable(EventSetDescriptor esd) {
1874    String eventName = esd.getName();
1875    return eventGeneratable(eventName);
1876  }
1877 
1878  /**
1879   * @param name of the event to check
1880   * @return true if eventName is one of the possible events
1881   * that this component can generate
1882   */
1883  private boolean generatableEvent(String eventName) {
1884    if (eventName.compareTo("graph") == 0
1885        || eventName.compareTo("text") == 0
1886        || eventName.compareTo("batchClassifier") == 0
1887        || eventName.compareTo("incrementalClassifier") == 0
1888        || eventName.compareTo("configuration") == 0) {
1889      return true;
1890    }
1891    return false;
1892  }
1893
1894  /**
1895   * Returns true, if at the current time, the named event could
1896   * be generated. Assumes that the supplied event name is
1897   * an event that could be generated by this bean
1898   *
1899   * @param eventName the name of the event in question
1900   * @return true if the named event could be generated at this point in
1901   * time
1902   */
1903  public boolean eventGeneratable(String eventName) {
1904    if (!generatableEvent(eventName)) {
1905      return false;
1906    }
1907    if (eventName.compareTo("graph") == 0) {
1908      // can't generate a GraphEvent if classifier is not drawable
1909      if (!(m_Classifier instanceof weka.core.Drawable)) {
1910        return false;
1911      }
1912      // need to have a training set before the classifier
1913      // can generate a graph!
1914      if (!m_listenees.containsKey("trainingSet")) {
1915        return false;
1916      }
1917      // Source needs to be able to generate a trainingSet
1918      // before we can generate a graph
1919      Object source = m_listenees.get("trainingSet");
1920       if (source instanceof EventConstraints) {
1921        if (!((EventConstraints)source).eventGeneratable("trainingSet")) {
1922          return false;
1923        }
1924      }
1925    }
1926
1927    if (eventName.compareTo("batchClassifier") == 0) {
1928      /*      if (!m_listenees.containsKey("testSet")) {
1929        return false;
1930      }
1931      if (!m_listenees.containsKey("trainingSet") &&
1932          m_trainingSet == null) {
1933        return false;
1934        } */
1935      if (!m_listenees.containsKey("testSet") && 
1936          !m_listenees.containsKey("trainingSet")) {
1937        return false;
1938      }
1939      Object source = m_listenees.get("testSet");
1940      if (source instanceof EventConstraints) {
1941        if (!((EventConstraints)source).eventGeneratable("testSet")) {
1942          return false;
1943        }
1944      }
1945      /*      source = m_listenees.get("trainingSet");
1946      if (source instanceof EventConstraints) {
1947        if (!((EventConstraints)source).eventGeneratable("trainingSet")) {
1948          return false;
1949        }
1950        } */
1951    }
1952
1953    if (eventName.compareTo("text") == 0) {
1954      if (!m_listenees.containsKey("trainingSet") &&
1955          !m_listenees.containsKey("instance")) {
1956        return false;
1957      }
1958      Object source = m_listenees.get("trainingSet");
1959      if (source != null && source instanceof EventConstraints) {
1960        if (!((EventConstraints)source).eventGeneratable("trainingSet")) {
1961          return false;
1962        }
1963      }
1964      source = m_listenees.get("instance");
1965      if (source != null && source instanceof EventConstraints) {
1966        if (!((EventConstraints)source).eventGeneratable("instance")) {
1967          return false;
1968        }
1969      }
1970    }
1971
1972    if (eventName.compareTo("incrementalClassifier") == 0) {
1973      /*      if (!(m_Classifier instanceof weka.classifiers.UpdateableClassifier)) {
1974        return false;
1975        } */
1976      if (!m_listenees.containsKey("instance")) {
1977        return false;
1978      }
1979      Object source = m_listenees.get("instance");
1980      if (source instanceof EventConstraints) {
1981        if (!((EventConstraints)source).eventGeneratable("instance")) {
1982          return false;
1983        }
1984      }
1985    }
1986   
1987    if (eventName.equals("configuration") && m_Classifier == null) {
1988      return false;
1989    }
1990   
1991    return true;
1992  }
1993   
1994  /**
1995   * Returns true if. at this time, the bean is busy with some
1996   * (i.e. perhaps a worker thread is performing some calculation).
1997   *
1998   * @return true if the bean is busy.
1999   */
2000  public boolean isBusy() {
2001    if (m_executorPool == null || 
2002        (m_executorPool.getQueue().size() == 0 && 
2003            m_executorPool.getActiveCount() == 0) && m_state == IDLE) {
2004      return false;
2005    }
2006    /* System.err.println("isBusy() Q:" + m_executorPool.getQueue().size()
2007        +" A:" + m_executorPool.getActiveCount()); */
2008    return true;
2009  }
2010 
2011  private String statusMessagePrefix() {
2012    return getCustomName() + "$" + hashCode() + "|"
2013    + ((m_Classifier instanceof OptionHandler &&
2014        Utils.joinOptions(((OptionHandler)m_Classifier).getOptions()).length() > 0) 
2015        ? Utils.joinOptions(((OptionHandler)m_Classifier).getOptions()) + "|"
2016            : "");
2017  }
2018}
Note: See TracBrowser for help on using the repository browser.