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