source: src/main/java/weka/gui/ensembleLibraryEditor/tree/GenericObjectNode.java @ 24

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

Import di weka.

File size: 20.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 *    GenericObjectNode.java
19 *    Copyright (C) 2006 Robert Jung
20 *
21 */
22
23package weka.gui.ensembleLibraryEditor.tree;
24
25import weka.classifiers.Classifier;
26import weka.gui.GenericObjectEditor;
27import weka.gui.ensembleLibraryEditor.AddModelsPanel;
28
29import java.awt.Component;
30import java.beans.BeanInfo;
31import java.beans.IntrospectionException;
32import java.beans.Introspector;
33import java.beans.MethodDescriptor;
34import java.beans.PropertyChangeEvent;
35import java.beans.PropertyChangeListener;
36import java.beans.PropertyDescriptor;
37import java.beans.PropertyEditor;
38import java.beans.PropertyEditorManager;
39import java.beans.PropertyVetoException;
40import java.lang.reflect.InvocationTargetException;
41import java.lang.reflect.Method;
42import java.util.Vector;
43
44import javax.swing.JFrame;
45import javax.swing.JOptionPane;
46import javax.swing.JPanel;
47import javax.swing.JTree;
48import javax.swing.tree.DefaultMutableTreeNode;
49import javax.swing.tree.DefaultTreeModel;
50
51/**
52 * This class is responsible for allowing users to choose an object that
53 * was provided with a GenericObjectEditor.  Just about every one of these
54 * Objects is a Weka Classifier.  There are two important things that these
55 * nodes are responsible for beyond the other parameter node types.  First,
56 * they must discover all of the parameters that need to be added in the
57 * model as child nodes.  This is done through a loop of introspection that
58 * was copied and adapted from the weka.gui.PropertySheetPanel class. 
59 * Second, this class is also responsible for discovering all possible
60 * combinations of GenericObject parameters that are stored in its child
61 * nodes.  This is accomplished by first discovering all of the child node
62 * parameters in the getValues method and then finding all combinations of
63 * these values with the combinAllValues method.
64 *
65 * @author  Robert Jung (mrbobjung@gmail.com)
66 * @version $Revision: 1.1 $
67 */
68public class GenericObjectNode
69  extends DefaultMutableTreeNode
70  implements PropertyChangeListener {
71 
72  /** for serialization */
73  private static final long serialVersionUID = 688096727663132485L;
74 
75  //The following 8 arrays hold the accumulated information about the
76  //Classifier parameters that we discover through introspection.  This
77  //is very similar to the approach within PropertySheetPanel.
78
79  /** Holds properties of the target */
80  private PropertyDescriptor m_Properties[];
81 
82  /** this tracks which indexes of the m_Properties */
83  private Vector m_UsedPropertyIndexes;
84 
85  /** Holds the methods of the target */
86  private MethodDescriptor m_Methods[];
87 
88  /** Holds property editors of the object */
89  private PropertyEditor m_Editors[];
90 
91  /** Holds current object values for each property */
92  private Object m_Values[];
93 
94  /** The labels for each property */
95  private String m_Names[];
96 
97  /** The tool tip text for each property */
98  private String m_TipTexts[];
99 
100  /** StringBuffer containing help text for the object being edited */
101  private StringBuffer m_HelpText;
102 
103  /** the GenericObjectEditor that was supplied for this node */
104  private GenericObjectEditor m_GenericObjectEditor;
105 
106  /** this Vector stores all of the possible combinations of parameters
107   * that it obtains from its child nodes.  These combinations are
108   * created by the recursive combineAllValues method*/
109  private Vector m_WorkingSetCombinations;
110 
111  /** the tip text for our node editor to display */
112  private String m_ToolTipText;
113 
114  /** a reference to the tree model is necessary to be able to add and
115   * remove nodes in the tree */
116  private DefaultTreeModel m_TreeModel;
117 
118  /** this is a reference to the Tree object that this node is
119   * contained within. Its required for this node to be able to
120   * add/remove nodes from the JTree*/
121  private JTree m_Tree;
122 
123  /** This is a reference to the parent panel of the JTree so that we can
124   * supply it as the required argument when supplying warning JDialog
125   * messages*/
126  private final AddModelsPanel m_ParentPanel;
127 
128  /**
129   * The constructor initialiazes the member variables of this node,
130   * Note that the "value" of this generic object is stored as the treeNode
131   * user object.
132   *
133   * @param panel       the reference to the parent panel for calls to JDialog
134   * @param value the value stored at this tree node
135   * @param genericObjectEditor the GenericObjectEditor for this object
136   * @param toolTipText the tipText to be displayed for this object
137   */
138  public GenericObjectNode(AddModelsPanel panel, Object value,
139      GenericObjectEditor genericObjectEditor, String toolTipText) {
140   
141    super(value);
142    //setObject(value);
143    m_ParentPanel = panel;
144    this.m_GenericObjectEditor = genericObjectEditor;
145    this.m_ToolTipText = toolTipText;
146   
147  }
148 
149  /**
150   * It seems kind of dumb that the reference to the tree model is passed in
151   * seperately - but know that this is actually necessary.  There is a really
152   * weird chicken before the egg problem.  You cannot create a TreeModel without
153   * giving it its root node.  However, the nodes in your tree can't update the
154   * structure of the tree unless they have a reference to the TreeModel.  So in
155   * the end this was the only compromise that I could get to work well
156   *
157   * @param tree        the tree to use
158   */
159  public void setTree(JTree tree) {
160    this.m_Tree = tree;
161    this.m_TreeModel = (DefaultTreeModel) m_Tree.getModel();
162   
163  }
164 
165  /**
166   * returns the current tree
167   *
168   * @return            the current tree
169   */
170  public JTree getTree() {
171    return m_Tree;
172  }
173 
174  /**
175   * A getter for the GenericObjectEditor for this node
176   *
177   * @return            the editor
178   */
179  public GenericObjectEditor getEditor() {
180    return m_GenericObjectEditor;
181  }
182 
183  /**
184   * getter for the tooltip text
185   *
186   * @return tooltip text
187   */
188  public StringBuffer getHelpText() {
189    return m_HelpText;
190  }
191 
192  /**
193   * getter for the tooltip text
194   *
195   * @return tooltip text
196   */
197  public String getToolTipText() {
198    return m_ToolTipText;
199  }
200 
201  /**
202   * getter for this node's object
203   *
204   * @return    the node's object
205   */
206  public Object getObject() {
207    return getUserObject();
208  }
209 
210  /**
211   * setter for this nodes object
212   *
213   * @param newValue    sets the new object
214   */
215  public void setObject(Object newValue) {
216    setUserObject(newValue);
217  }
218 
219  /**
220   * this is a simple filter for the setUserObject method.  We basically
221   * don't want null values to be passed in.
222   *
223   * @param o           the object to set
224   */
225  public void setUserObject(Object o) {
226    if (o != null)
227      super.setUserObject(o);
228  }
229 
230  /**
231   * getter for the parent panel
232   *
233   * @return            the parent panel
234   */
235  public JPanel getParentPanel() {
236    return m_ParentPanel;
237  }
238 
239  /**
240   * returns always null
241   *
242   * @return            always null
243   */
244  public String toString() {
245    return null;
246    //return getClass().getName() + "[" + getUserObject().toString() + "]";
247  }
248 
249  /**
250   * This implements the PropertyChangeListener for this node that gets
251   * registered with its Editor.  All we really have to do is change the
252   * Object value stored internally at this node when its editor says the
253   * value changed.
254   *
255   * @param evt         the event
256   */
257  public void propertyChange(PropertyChangeEvent evt) {
258   
259    Object newValue = ((GenericObjectEditor) evt.getSource()).getValue();
260   
261    if (!newValue.getClass().equals(getObject().getClass())) {
262     
263      if (m_TreeModel.getRoot() == this) {
264       
265        try {
266          m_ParentPanel.buildClassifierTree((Classifier) newValue
267              .getClass().newInstance());
268        } catch (InstantiationException e) {
269          e.printStackTrace();
270        } catch (IllegalAccessException e) {
271          e.printStackTrace();
272        }
273        m_ParentPanel.update(m_ParentPanel.getGraphics());
274        m_ParentPanel.repaint();
275       
276        //System.out.println("Changed root");
277       
278      } else {
279        setObject(newValue);
280        updateTree();
281        updateTree();
282        m_TreeModel.nodeChanged(this);
283      }
284    }
285  }
286 
287  /**
288   * This method uses introspection to programatically discover all of
289   * the parameters for this generic object.  For each one of them it
290   * uses the TreeModel reference to create a new subtree to represent
291   * that parameter and its value ranges.  Note that all of these nodes
292   * are PropertyNodes which themselves hold the logic of figuring out
293   * what type of parameter it is they are representing and thus what
294   * type of subtree to build.
295   * <p/>
296   * We need to be careful because this was molded from the code inside of
297   * the PropertySheetPanel class.  Which means that we are wide open
298   * to copy/paste problems.  In the future, when that code changes to
299   * adapt to other changes in Weka then this could easily become broken.
300   */
301  public void updateTree() {
302   
303    int childCount = m_TreeModel.getChildCount(this);
304   
305    for (int i = 0; i < childCount; i++) {
306      DefaultMutableTreeNode child = (DefaultMutableTreeNode) m_TreeModel.getChild(this, 0);
307     
308      m_TreeModel.removeNodeFromParent(child);
309    }
310   
311    //removeAllChildren();
312   
313    Object classifier = this.getUserObject();
314   
315    try {
316      BeanInfo bi = Introspector.getBeanInfo(classifier.getClass());
317      m_Properties = bi.getPropertyDescriptors();
318      m_Methods = bi.getMethodDescriptors();
319    } catch (IntrospectionException ex) {
320      System.err.println("PropertySheet: Couldn't introspect");
321      return;
322    }
323   
324    //           Look for a globalInfo method that returns a string
325    // describing the target
326    for (int i = 0; i < m_Methods.length; i++) {
327      String name = m_Methods[i].getDisplayName();
328      Method meth = m_Methods[i].getMethod();
329      if (name.equals("globalInfo")) {
330        if (meth.getReturnType().equals(String.class)) {
331          try {
332            Object args[] = {};
333            String globalInfo = (String) (meth.invoke(getObject(),
334                args));
335            String summary = globalInfo;
336            int ci = globalInfo.indexOf('.');
337            if (ci != -1) {
338              summary = globalInfo.substring(0, ci + 1);
339            }
340            final String className = getObject().getClass().getName();
341            m_HelpText = new StringBuffer("NAME\n");
342            m_HelpText.append(className).append("\n\n");
343            m_HelpText.append("SYNOPSIS\n").append(globalInfo).append("\n\n");
344           
345          } catch (Exception ex) {
346            // ignored
347          }
348        }
349      }
350    }
351   
352    m_UsedPropertyIndexes = new Vector();
353   
354    m_Editors = new PropertyEditor[m_Properties.length];
355   
356    m_Values = new Object[m_Properties.length];
357    m_Names = new String[m_Properties.length];
358    m_TipTexts = new String[m_Properties.length];
359    boolean firstTip = true;
360   
361    for (int i = 0; i < m_Properties.length; i++) {
362     
363      // Don't display hidden or expert properties.
364      if (m_Properties[i].isHidden() || m_Properties[i].isExpert()) {
365        continue;
366      }
367     
368      m_Names[i] = m_Properties[i].getDisplayName();
369      Class type = m_Properties[i].getPropertyType();
370      Method getter = m_Properties[i].getReadMethod();
371      Method setter = m_Properties[i].getWriteMethod();
372     
373      // Only display read/write properties.
374      if (getter == null || setter == null) {
375        continue;
376      }
377     
378      try {
379        Object args[] = {};
380        Object value = getter.invoke(classifier, args);
381        m_Values[i] = value;
382       
383        PropertyEditor editor = null;
384        Class pec = m_Properties[i].getPropertyEditorClass();
385        if (pec != null) {
386          try {
387            editor = (PropertyEditor) pec.newInstance();
388          } catch (Exception ex) {
389            // Drop through.
390          }
391        }
392        if (editor == null) {
393          editor = PropertyEditorManager.findEditor(type);
394        }
395        m_Editors[i] = editor;
396       
397        // If we can't edit this component, skip it.
398        if (editor == null) {
399          continue;
400        }
401        if (editor instanceof GenericObjectEditor) {
402          ((GenericObjectEditor) editor).setClassType(type);
403        }
404       
405        // Don't try to set null values:
406        if (value == null) {
407          continue;
408        }
409       
410        editor.setValue(value);
411       
412        // now look for a TipText method for this property
413        String tipName = m_Names[i] + "TipText";
414        for (int j = 0; j < m_Methods.length; j++) {
415          String mname = m_Methods[j].getDisplayName();
416          Method meth = m_Methods[j].getMethod();
417          if (mname.equals(tipName)) {
418            if (meth.getReturnType().equals(String.class)) {
419              try {
420                String tempTip = (String) (meth.invoke(
421                    classifier, args));
422                int ci = tempTip.indexOf('.');
423                if (ci < 0) {
424                  m_TipTexts[i] = tempTip;
425                } else {
426                  m_TipTexts[i] = tempTip.substring(0, ci);
427                }
428               
429                if (m_HelpText != null) {
430                  if (firstTip) {
431                    m_HelpText.append("OPTIONS\n");
432                    firstTip = false;
433                  }
434                  m_HelpText.append(m_Names[i]).append(" -- ");
435                  m_HelpText.append(tempTip).append("\n\n");
436                 
437                }
438               
439              } catch (Exception ex) {
440               
441              }
442              break;
443            }
444          }
445        }
446       
447        //Here we update the usedPropertyIndexes variable so that
448        //later on we will know which ones to look at.
449        m_UsedPropertyIndexes.add(new Integer(i));
450       
451        int currentCount = m_TreeModel.getChildCount(this);
452       
453        //Now we make a child node and add it to the tree underneath
454        //this one
455        PropertyNode newNode = new PropertyNode(m_Tree, m_ParentPanel,
456            m_Names[i], m_TipTexts[i], m_Values[i], m_Editors[i]);
457       
458        m_TreeModel.insertNodeInto(newNode, this, currentCount);
459       
460      } catch (InvocationTargetException ex) {
461        System.err.println("Skipping property " + m_Names[i]
462                                                          + " ; exception on target: " + ex.getTargetException());
463        ex.getTargetException().printStackTrace();
464        continue;
465      } catch (Exception ex) {
466        System.err.println("Skipping property " + m_Names[i]
467                                                          + " ; exception: " + ex);
468        ex.printStackTrace();
469        continue;
470      }
471     
472    }
473   
474    //Finally we tell the TreeModel to update itself so the changes
475    //will be visible
476    m_TreeModel.nodeStructureChanged(this);
477  }
478 
479  /**
480   * This method iterates over all of the child nodes of this
481   * GenericObjectNode and requests the verious sets of values that the
482   * user has presumably specified.  Once these sets of values are
483   *
484   * @return a Vector consisting of all parameter combinations
485   */
486  public Vector getValues() {
487   
488    Vector valuesVector = new Vector();
489   
490    int childCount = m_TreeModel.getChildCount(this);
491   
492    //poll all child nodes for their values.
493    for (int i = 0; i < childCount; i++) {
494     
495      PropertyNode currentChild = (PropertyNode) m_TreeModel.getChild(
496          this, i);
497     
498      Vector v = currentChild.getAllValues();
499      valuesVector.add(v);
500     
501    }
502   
503    //Need to initialize the working set of paramter combinations
504    m_WorkingSetCombinations = new Vector();
505   
506    //obtain all combinations of the paremeters
507    combineAllValues(new Vector(), valuesVector);
508   
509    /*
510     //nice for initially debugging this - and there was a WHOLE lot of
511      //that going on till this crazy idea finally worked.
512       for (int i = 0; i < m_WorkingSetCombinations.size(); i++) {
513       
514       System.out.print("Combo "+i+": ");
515       
516       Vector current = (Vector)m_WorkingSetCombinations.get(i);
517       for (int j = 0; j < current.size(); j++) {
518       
519       System.out.print(current.get(j)+"\t");
520       
521       }
522       
523       System.out.print("\n");
524       }
525       */
526   
527    //Now the real work begins.  Here we need to translate all of the values
528    //received from the editors back into the actual class types that the
529    //Weka classifiers will understand.  for example, String values for
530    //enumerated values need to be turned back into the SelectedTag objects
531    //that classifiers understand.
532    //This vector will hold all of the actual generic objects that are being
533    //instantiated
534    Vector newGenericObjects = new Vector();
535   
536    for (int i = 0; i < m_WorkingSetCombinations.size(); i++) {
537     
538      Vector current = (Vector) m_WorkingSetCombinations.get(i);
539     
540      //create a new copy of this class.  We will use this copy to test whether
541      //the current set of parameters is valid.
542      Object o = this.getUserObject();
543      Class c = o.getClass();
544      Object copy = null;
545     
546      try {
547        copy = c.newInstance();
548      } catch (InstantiationException e) {
549        e.printStackTrace();
550      } catch (IllegalAccessException e) {
551        e.printStackTrace();
552      }
553     
554      for (int j = 0; j < current.size(); j++) {
555       
556        Object[] args = new Object[1];
557       
558        int index = ((Integer) m_UsedPropertyIndexes.get(j)).intValue();
559       
560        PropertyDescriptor property = (PropertyDescriptor) m_Properties[index];
561        Method setter = property.getWriteMethod();
562        Class[] params = setter.getParameterTypes();
563       
564        Object currentVal = current.get(j);
565       
566        //System.out.println(currentVal.getClass().toString());
567       
568        //we gotta turn strings back into booleans
569        if (params.length == 1
570            && params[0].toString().equals("boolean")
571            && currentVal.getClass().toString().equals(
572            "class java.lang.String")) {
573         
574          currentVal = new Boolean((String) current.get(j));
575        }
576       
577        //we gotta turn strings back into "Tags"
578        if (params.length == 1
579            && params[0].toString().equals(
580            "class weka.core.SelectedTag")
581            && currentVal.getClass().toString().equals(
582            "class java.lang.String")) {
583         
584          String tagString = (String) current.get(j);
585         
586          m_Editors[index].setAsText(tagString);
587          currentVal = m_Editors[index].getValue();
588         
589        }
590       
591        args[0] = currentVal;
592       
593        /*
594         System.out.print("setterName: "+setter.getName()+
595         " editor class: "+m_Editors[index].getClass()+
596         " params: ");
597         
598         
599         for (int k = 0; k < params.length; k++)
600         System.out.print(params[k].toString()+" ");
601         
602         System.out.println(" value class: "+args[0].getClass().toString());
603         */
604       
605        try {
606         
607          //we tell the setter for the current parameter to update the copy
608          //with the current parameter value
609          setter.invoke(copy, args);
610         
611        } catch (InvocationTargetException ex) {
612          if (ex.getTargetException() instanceof PropertyVetoException) {
613            String message = "WARNING: Vetoed; reason is: "
614              + ex.getTargetException().getMessage();
615            System.err.println(message);
616           
617            Component jf;
618            jf = m_ParentPanel.getRootPane();
619            JOptionPane.showMessageDialog(jf, message, "error",
620                JOptionPane.WARNING_MESSAGE);
621            if (jf instanceof JFrame)
622              ((JFrame) jf).dispose();
623           
624          } else {
625            System.err.println(ex.getTargetException().getClass()
626                .getName()
627                + " while updating "
628                + property.getName()
629                + ": " + ex.getTargetException().getMessage());
630            Component jf;
631            jf = m_ParentPanel.getRootPane();
632            JOptionPane.showMessageDialog(jf, ex
633                .getTargetException().getClass().getName()
634                + " while updating "
635                + property.getName()
636                + ":\n" + ex.getTargetException().getMessage(),
637                "error", JOptionPane.WARNING_MESSAGE);
638            if (jf instanceof JFrame)
639              ((JFrame) jf).dispose();
640           
641          }
642         
643        } catch (IllegalArgumentException e) {
644          e.printStackTrace();
645        } catch (IllegalAccessException e) {
646          e.printStackTrace();
647        }
648       
649      }
650     
651      //At this point we have set all the parameters for this GenericObject
652      //with a single combination that was generated from the
653      //m_WorkingSetCombinations Vector and can add it to the collection that
654      //will be returned
655      newGenericObjects.add(copy);
656     
657    }
658   
659    return newGenericObjects;
660  }
661 
662  /** This method is responsible for returning all possible values through
663   * a recursive loop.
664   *
665   * When the recursion terminates that means that there are no more parameter
666   * sets to branch out through so all we have to do is save the current Vector
667   * in the working set of combinations.  Otherwise we iterate through all
668   * possible values left in the next set of parameter values and recursively
669   * call this function.
670   *
671   * @param previouslySelected stores the values chosen in this branch of the recursion
672   * @param remainingValues the sets of values left
673   */
674  public void combineAllValues(Vector previouslySelected,
675      Vector remainingValues) {
676   
677    if (remainingValues.isEmpty()) {
678      m_WorkingSetCombinations.add(previouslySelected);
679      return;
680    }
681   
682    Vector currentSet = new Vector((Vector) remainingValues.get(0));
683    Vector tmpRemaining = new Vector(remainingValues);
684    tmpRemaining.removeElementAt(0);
685   
686    for (int i = 0; i < currentSet.size(); i++) {
687      Vector tmpPreviouslySelected = new Vector(previouslySelected);
688      tmpPreviouslySelected.add(currentSet.get(i));
689      combineAllValues(tmpPreviouslySelected, tmpRemaining);
690    }
691   
692  }
693 
694}
Note: See TracBrowser for help on using the repository browser.