2
* This program is free software; you can redistribute it and/or modify
3
* it under the terms of the GNU General Public License as published by
4
* the Free Software Foundation; either version 2 of the License, or
5
* (at your option) any later version.
7
* This program is distributed in the hope that it will be useful,
8
* but WITHOUT ANY WARRANTY; without even the implied warranty of
9
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
* GNU General Public License for more details.
12
* You should have received a copy of the GNU General Public License
13
* along with this program; if not, write to the Free Software
14
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
* GenericObjectNode.java
19
* Copyright (C) 2006 Robert Jung
23
package weka.gui.ensembleLibraryEditor.tree;
25
import weka.classifiers.Classifier;
26
import weka.gui.GenericObjectEditor;
27
import weka.gui.ensembleLibraryEditor.AddModelsPanel;
29
import java.awt.Component;
30
import java.beans.BeanInfo;
31
import java.beans.IntrospectionException;
32
import java.beans.Introspector;
33
import java.beans.MethodDescriptor;
34
import java.beans.PropertyChangeEvent;
35
import java.beans.PropertyChangeListener;
36
import java.beans.PropertyDescriptor;
37
import java.beans.PropertyEditor;
38
import java.beans.PropertyEditorManager;
39
import java.beans.PropertyVetoException;
40
import java.lang.reflect.InvocationTargetException;
41
import java.lang.reflect.Method;
42
import java.util.Vector;
44
import javax.swing.JFrame;
45
import javax.swing.JOptionPane;
46
import javax.swing.JPanel;
47
import javax.swing.JTree;
48
import javax.swing.tree.DefaultMutableTreeNode;
49
import javax.swing.tree.DefaultTreeModel;
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.
65
* @author Robert Jung (mrbobjung@gmail.com)
66
* @version $Revision: 1.1 $
68
public class GenericObjectNode
69
extends DefaultMutableTreeNode
70
implements PropertyChangeListener {
72
/** for serialization */
73
private static final long serialVersionUID = 688096727663132485L;
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.
79
/** Holds properties of the target */
80
private PropertyDescriptor m_Properties[];
82
/** this tracks which indexes of the m_Properties */
83
private Vector m_UsedPropertyIndexes;
85
/** Holds the methods of the target */
86
private MethodDescriptor m_Methods[];
88
/** Holds property editors of the object */
89
private PropertyEditor m_Editors[];
91
/** Holds current object values for each property */
92
private Object m_Values[];
94
/** The labels for each property */
95
private String m_Names[];
97
/** The tool tip text for each property */
98
private String m_TipTexts[];
100
/** StringBuffer containing help text for the object being edited */
101
private StringBuffer m_HelpText;
103
/** the GenericObjectEditor that was supplied for this node */
104
private GenericObjectEditor m_GenericObjectEditor;
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;
111
/** the tip text for our node editor to display */
112
private String m_ToolTipText;
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;
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;
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
126
private final AddModelsPanel m_ParentPanel;
129
* The constructor initialiazes the member variables of this node,
130
* Note that the "value" of this generic object is stored as the treeNode
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
138
public GenericObjectNode(AddModelsPanel panel, Object value,
139
GenericObjectEditor genericObjectEditor, String toolTipText) {
143
m_ParentPanel = panel;
144
this.m_GenericObjectEditor = genericObjectEditor;
145
this.m_ToolTipText = toolTipText;
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
157
* @param tree the tree to use
159
public void setTree(JTree tree) {
161
this.m_TreeModel = (DefaultTreeModel) m_Tree.getModel();
166
* returns the current tree
168
* @return the current tree
170
public JTree getTree() {
175
* A getter for the GenericObjectEditor for this node
179
public GenericObjectEditor getEditor() {
180
return m_GenericObjectEditor;
184
* getter for the tooltip text
186
* @return tooltip text
188
public StringBuffer getHelpText() {
193
* getter for the tooltip text
195
* @return tooltip text
197
public String getToolTipText() {
198
return m_ToolTipText;
202
* getter for this node's object
204
* @return the node's object
206
public Object getObject() {
207
return getUserObject();
211
* setter for this nodes object
213
* @param newValue sets the new object
215
public void setObject(Object newValue) {
216
setUserObject(newValue);
220
* this is a simple filter for the setUserObject method. We basically
221
* don't want null values to be passed in.
223
* @param o the object to set
225
public void setUserObject(Object o) {
227
super.setUserObject(o);
231
* getter for the parent panel
233
* @return the parent panel
235
public JPanel getParentPanel() {
236
return m_ParentPanel;
240
* returns always null
242
* @return always null
244
public String toString() {
246
//return getClass().getName() + "[" + getUserObject().toString() + "]";
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
255
* @param evt the event
257
public void propertyChange(PropertyChangeEvent evt) {
259
Object newValue = ((GenericObjectEditor) evt.getSource()).getValue();
261
if (!newValue.getClass().equals(getObject().getClass())) {
263
if (m_TreeModel.getRoot() == this) {
266
m_ParentPanel.buildClassifierTree((Classifier) newValue
267
.getClass().newInstance());
268
} catch (InstantiationException e) {
270
} catch (IllegalAccessException e) {
273
m_ParentPanel.update(m_ParentPanel.getGraphics());
274
m_ParentPanel.repaint();
276
//System.out.println("Changed root");
282
m_TreeModel.nodeChanged(this);
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.
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.
301
public void updateTree() {
303
int childCount = m_TreeModel.getChildCount(this);
305
for (int i = 0; i < childCount; i++) {
306
DefaultMutableTreeNode child = (DefaultMutableTreeNode) m_TreeModel.getChild(this, 0);
308
m_TreeModel.removeNodeFromParent(child);
311
//removeAllChildren();
313
Object classifier = this.getUserObject();
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");
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)) {
333
String globalInfo = (String) (meth.invoke(getObject(),
335
String summary = globalInfo;
336
int ci = globalInfo.indexOf('.');
338
summary = globalInfo.substring(0, ci + 1);
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");
345
} catch (Exception ex) {
352
m_UsedPropertyIndexes = new Vector();
354
m_Editors = new PropertyEditor[m_Properties.length];
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;
361
for (int i = 0; i < m_Properties.length; i++) {
363
// Don't display hidden or expert properties.
364
if (m_Properties[i].isHidden() || m_Properties[i].isExpert()) {
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();
373
// Only display read/write properties.
374
if (getter == null || setter == null) {
380
Object value = getter.invoke(classifier, args);
383
PropertyEditor editor = null;
384
Class pec = m_Properties[i].getPropertyEditorClass();
387
editor = (PropertyEditor) pec.newInstance();
388
} catch (Exception ex) {
392
if (editor == null) {
393
editor = PropertyEditorManager.findEditor(type);
395
m_Editors[i] = editor;
397
// If we can't edit this component, skip it.
398
if (editor == null) {
401
if (editor instanceof GenericObjectEditor) {
402
((GenericObjectEditor) editor).setClassType(type);
405
// Don't try to set null values:
410
editor.setValue(value);
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)) {
420
String tempTip = (String) (meth.invoke(
422
int ci = tempTip.indexOf('.');
424
m_TipTexts[i] = tempTip;
426
m_TipTexts[i] = tempTip.substring(0, ci);
429
if (m_HelpText != null) {
431
m_HelpText.append("OPTIONS\n");
434
m_HelpText.append(m_Names[i]).append(" -- ");
435
m_HelpText.append(tempTip).append("\n\n");
439
} catch (Exception ex) {
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));
451
int currentCount = m_TreeModel.getChildCount(this);
453
//Now we make a child node and add it to the tree underneath
455
PropertyNode newNode = new PropertyNode(m_Tree, m_ParentPanel,
456
m_Names[i], m_TipTexts[i], m_Values[i], m_Editors[i]);
458
m_TreeModel.insertNodeInto(newNode, this, currentCount);
460
} catch (InvocationTargetException ex) {
461
System.err.println("Skipping property " + m_Names[i]
462
+ " ; exception on target: " + ex.getTargetException());
463
ex.getTargetException().printStackTrace();
465
} catch (Exception ex) {
466
System.err.println("Skipping property " + m_Names[i]
467
+ " ; exception: " + ex);
468
ex.printStackTrace();
474
//Finally we tell the TreeModel to update itself so the changes
476
m_TreeModel.nodeStructureChanged(this);
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
484
* @return a Vector consisting of all parameter combinations
486
public Vector getValues() {
488
Vector valuesVector = new Vector();
490
int childCount = m_TreeModel.getChildCount(this);
492
//poll all child nodes for their values.
493
for (int i = 0; i < childCount; i++) {
495
PropertyNode currentChild = (PropertyNode) m_TreeModel.getChild(
498
Vector v = currentChild.getAllValues();
503
//Need to initialize the working set of paramter combinations
504
m_WorkingSetCombinations = new Vector();
506
//obtain all combinations of the paremeters
507
combineAllValues(new Vector(), valuesVector);
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++) {
514
System.out.print("Combo "+i+": ");
516
Vector current = (Vector)m_WorkingSetCombinations.get(i);
517
for (int j = 0; j < current.size(); j++) {
519
System.out.print(current.get(j)+"\t");
523
System.out.print("\n");
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
534
Vector newGenericObjects = new Vector();
536
for (int i = 0; i < m_WorkingSetCombinations.size(); i++) {
538
Vector current = (Vector) m_WorkingSetCombinations.get(i);
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();
547
copy = c.newInstance();
548
} catch (InstantiationException e) {
550
} catch (IllegalAccessException e) {
554
for (int j = 0; j < current.size(); j++) {
556
Object[] args = new Object[1];
558
int index = ((Integer) m_UsedPropertyIndexes.get(j)).intValue();
560
PropertyDescriptor property = (PropertyDescriptor) m_Properties[index];
561
Method setter = property.getWriteMethod();
562
Class[] params = setter.getParameterTypes();
564
Object currentVal = current.get(j);
566
//System.out.println(currentVal.getClass().toString());
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")) {
574
currentVal = new Boolean((String) current.get(j));
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")) {
584
String tagString = (String) current.get(j);
586
m_Editors[index].setAsText(tagString);
587
currentVal = m_Editors[index].getValue();
591
args[0] = currentVal;
594
System.out.print("setterName: "+setter.getName()+
595
" editor class: "+m_Editors[index].getClass()+
599
for (int k = 0; k < params.length; k++)
600
System.out.print(params[k].toString()+" ");
602
System.out.println(" value class: "+args[0].getClass().toString());
607
//we tell the setter for the current parameter to update the copy
608
//with the current parameter value
609
setter.invoke(copy, args);
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);
618
jf = m_ParentPanel.getRootPane();
619
JOptionPane.showMessageDialog(jf, message, "error",
620
JOptionPane.WARNING_MESSAGE);
621
if (jf instanceof JFrame)
622
((JFrame) jf).dispose();
625
System.err.println(ex.getTargetException().getClass()
629
+ ": " + ex.getTargetException().getMessage());
631
jf = m_ParentPanel.getRootPane();
632
JOptionPane.showMessageDialog(jf, ex
633
.getTargetException().getClass().getName()
636
+ ":\n" + ex.getTargetException().getMessage(),
637
"error", JOptionPane.WARNING_MESSAGE);
638
if (jf instanceof JFrame)
639
((JFrame) jf).dispose();
643
} catch (IllegalArgumentException e) {
645
} catch (IllegalAccessException e) {
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
655
newGenericObjects.add(copy);
659
return newGenericObjects;
662
/** This method is responsible for returning all possible values through
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.
671
* @param previouslySelected stores the values chosen in this branch of the recursion
672
* @param remainingValues the sets of values left
674
public void combineAllValues(Vector previouslySelected,
675
Vector remainingValues) {
677
if (remainingValues.isEmpty()) {
678
m_WorkingSetCombinations.add(previouslySelected);
682
Vector currentSet = new Vector((Vector) remainingValues.get(0));
683
Vector tmpRemaining = new Vector(remainingValues);
684
tmpRemaining.removeElementAt(0);
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);
b'\\ No newline at end of file'