weka.gui.GenericObjectEditor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of weka-stable Show documentation
Show all versions of weka-stable Show documentation
The Waikato Environment for Knowledge Analysis (WEKA), a machine
learning workbench. This is the stable version. Apart from bugfixes, this version
does not receive any other updates.
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/*
* GenericObjectEditor.java
* Copyright (C) 2002-2012 University of Waikato, Hamilton, New Zealand
*
*/
package weka.gui;
import weka.core.Capabilities;
import weka.core.Capabilities.Capability;
import weka.core.CapabilitiesHandler;
import weka.core.ClassDiscovery;
import weka.core.CustomDisplayStringProvider;
import weka.core.OptionHandler;
import weka.core.SerializedObject;
import weka.core.Utils;
import weka.core.WekaPackageManager;
import weka.core.logging.Logger;
import weka.gui.CheckBoxList.CheckBoxListModel;
import weka.core.PluginManager;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.ToolTipManager;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;
/**
* A PropertyEditor for objects. It can be used either in a static or a dynamic
* way.
*
* In the static way (USE_DYNAMIC
is false
) the
* objects have been defined as editable in the GenericObjectEditor
* configuration file, which lists possible values that can be selected from,
* and themselves configured. The configuration file is called
* "GenericObjectEditor.props" and may live in either the location given by
* "user.home" or the current directory (this last will take precedence), and a
* default properties file is read from the Weka distribution. For speed, the
* properties file is read only once when the class is first loaded -- this may
* need to be changed if we ever end up running in a Java OS ;-).
*
* If it is used in a dynamic way (the UseDynamic
property
* of the GenericPropertiesCreator props file is set to true
) then
* the classes to list are discovered by the
* GenericPropertiesCreator
class (it checks the complete
* classpath).
*
* @see GenericPropertiesCreator
* @see GenericPropertiesCreator#useDynamic()
* @see GenericPropertiesCreator#CREATOR_FILE
* @see weka.core.ClassDiscovery
*
* @author Len Trigg ([email protected])
* @author Xin Xu ([email protected])
* @author Richard Kirkby ([email protected])
* @author FracPete (fracpete at waikato dot ac dot nz)
* @version $Revision: 12409 $
*/
public class GenericObjectEditor implements PropertyEditor, CustomPanelSupplier {
/** The object being configured. */
protected Object m_Object;
/**
* Holds a copy of the current object that can be reverted to if the user
* decides to cancel.
*/
protected Object m_Backup;
/** Handles property change notification. */
protected PropertyChangeSupport m_Support = new PropertyChangeSupport(this);
/** The Class of objects being edited. */
protected Class> m_ClassType;
/** The model containing the list of names to select from. */
protected Hashtable m_ObjectNames;
/** The GUI component for editing values, created when needed. */
protected GOEPanel m_EditorComponent;
/** True if the GUI component is needed. */
protected boolean m_Enabled = true;
/** The name of the properties file. */
protected static String PROPERTY_FILE = "weka/gui/GenericObjectEditor.props";
/** Contains the editor properties. */
protected static Properties EDITOR_PROPERTIES;
/** the properties files containing the class/editor mappings. */
public static final String GUIEDITORS_PROPERTY_FILE = "weka/gui/GUIEditors.props";
/** The tree node of the current object so we can re-select it for the user. */
protected GOETreeNode m_treeNodeOfCurrentObject;
/** The property panel created for the objects. */
protected PropertyPanel m_ObjectPropertyPanel;
/** whether the class can be changed. */
protected boolean m_canChangeClassInDialog;
/** the history of used setups. */
protected GenericObjectEditorHistory m_History;
/** whether the Weka Editors were already registered. */
protected static boolean m_EditorsRegistered;
/** whether to display the global info tool tip in the tree. */
protected static boolean m_ShowGlobalInfoToolTip;
/** for filtering the tree based on the Capabilities of the leaves. */
protected Capabilities m_CapabilitiesFilter = null;
public static void setShowGlobalInfoToolTips(boolean show) {
m_ShowGlobalInfoToolTip = show;
}
public boolean getShowGlobalInfoToolTips() {
return m_ShowGlobalInfoToolTip;
}
public static void determineClasses() {
try {
// make sure we load all packages first!!!
WekaPackageManager.loadPackages(false);
// Don't do anything else until all initial packages are loaded...
if (WekaPackageManager.m_initialPackageLoadingInProcess) {
return;
}
EDITOR_PROPERTIES = GenericPropertiesCreator.getGlobalOutputProperties();
if (EDITOR_PROPERTIES == null) {
// try creating a new one from scratch
GenericPropertiesCreator creator = new GenericPropertiesCreator();
// dynamic approach?
if (creator.useDynamic()) {
try {
creator.execute(false);
EDITOR_PROPERTIES = creator.getOutputProperties();
} catch (Exception e) {
JOptionPane.showMessageDialog(null,
"Could not determine the properties for the generic object\n"
+ "editor. This exception was produced:\n" + e.toString(),
"GenericObjectEditor", JOptionPane.ERROR_MESSAGE);
}
} else {
// Allow a properties file in the current directory to override
try {
EDITOR_PROPERTIES = Utils.readProperties(PROPERTY_FILE);
java.util.Enumeration> keys = EDITOR_PROPERTIES.propertyNames();
if (!keys.hasMoreElements()) {
throw new Exception("Failed to read a property file for the "
+ "generic object editor");
}
} catch (Exception ex) {
JOptionPane
.showMessageDialog(
null,
"Could not read a configuration file for the generic object\n"
+ "editor. An example file is included with the Weka distribution.\n"
+ "This file should be named \"" + PROPERTY_FILE + "\" and\n"
+ "should be placed either in your user home (which is set\n"
+ "to \"" + System.getProperties().getProperty("user.home")
+ "\")\n" + "or the directory that java was started from\n",
"GenericObjectEditor", JOptionPane.ERROR_MESSAGE);
}
}
}
if (EDITOR_PROPERTIES == null) {
JOptionPane.showMessageDialog(null,
"Could not initialize the GenericPropertiesCreator. ",
"GenericObjectEditor", JOptionPane.ERROR_MESSAGE);
} else {
PluginManager.addFromProperties(EDITOR_PROPERTIES);
}
} catch (Exception e) {
JOptionPane.showMessageDialog(null,
"Could not initialize the GenericPropertiesCreator. "
+ "This exception was produced:\n" + e.toString(),
"GenericObjectEditor", JOptionPane.ERROR_MESSAGE);
}
}
/**
* Loads the configuration property file (USE_DYNAMIC is FALSE) or determines
* the classes dynamically (USE_DYNAMIC is TRUE)
*
* @see #USE_DYNAMIC
* @see GenericPropertiesCreator
*/
static {
determineClasses();
}
/**
* A specialized TreeNode for supporting filtering via Capabilities.
*/
public class GOETreeNode extends DefaultMutableTreeNode {
/** for serialization. */
static final long serialVersionUID = -1707872446682150133L;
/** color for "no support". */
public final static String NO_SUPPORT = "silver";
/** color for "maybe support". */
public final static String MAYBE_SUPPORT = "blue";
/** the Capabilities object to use for filtering. */
protected Capabilities m_Capabilities = null;
/** tool tip */
protected String m_toolTipText;
/**
* Creates a tree node that has no parent and no children, but which allows
* children.
*/
public GOETreeNode() {
super();
}
/**
* Creates a tree node with no parent, no children, but which allows
* children, and initializes it with the specified user object.
*
* @param userObject an Object provided by the user that constitutes the
* node's data
*/
public GOETreeNode(Object userObject) {
super(userObject);
}
/**
* Creates a tree node with no parent, no children, initialized with the
* specified user object, and that allows children only if specified.
*
* @param userObject an Object provided by the user that constitutes the
* node's data
* @param allowsChildren if true, the node is allowed to have child nodes --
* otherwise, it is always a leaf node
*/
public GOETreeNode(Object userObject, boolean allowsChildren) {
super(userObject, allowsChildren);
}
/**
* Set the tool tip for this node
*
* @param tip the tool tip for this node
*/
public void setToolTipText(String tip) {
m_toolTipText = tip;
}
/**
* Get the tool tip for this node
*
* @return the tool tip for this node
*/
public String getToolTipText() {
return m_toolTipText;
}
/**
* generates if necessary a Capabilities object for the given leaf.
*/
protected void initCapabilities() {
String classname;
Class> cls;
Object obj;
if (m_Capabilities != null) {
return;
}
if (!isLeaf()) {
return;
}
classname = getClassnameFromPath(new TreePath(getPath()));
try {
cls = Class.forName(classname);
if (!ClassDiscovery.hasInterface(CapabilitiesHandler.class, cls)) {
return;
}
obj = cls.newInstance();
m_Capabilities = ((CapabilitiesHandler) obj).getCapabilities();
} catch (Exception e) {
// ignore it
}
}
/**
* returns a string representation of this treenode.
*
* @return the text to display
*/
@Override
public String toString() {
String result;
result = super.toString();
if (m_CapabilitiesFilter != null) {
initCapabilities();
if (m_Capabilities != null) {
if (m_Capabilities.supportsMaybe(m_CapabilitiesFilter)
&& !m_Capabilities.supports(m_CapabilitiesFilter)) {
result = "" + result
+ "";
} else if (!m_Capabilities.supports(m_CapabilitiesFilter)) {
result = "" + result
+ "";
}
}
}
return result;
}
}
/**
* A dialog for selecting Capabilities to look for in the GOE tree.
*/
public class CapabilitiesFilterDialog extends JDialog {
/** for serialization. */
static final long serialVersionUID = -7845503345689646266L;
/** the dialog itself. */
protected JDialog m_Self;
/** the popup to display again. */
protected JPopupMenu m_Popup = null;
/** the capabilities used for initializing the dialog. */
protected Capabilities m_Capabilities = new Capabilities(null);
/** the label, listing the name of the superclass. */
protected JLabel m_InfoLabel = new JLabel();
/** the list with all the capabilities. */
protected CheckBoxList m_List = new CheckBoxList();
/** the OK button. */
protected JButton m_OkButton = new JButton("OK");
/** the Cancel button. */
protected JButton m_CancelButton = new JButton("Cancel");
/**
* creates a dialog to choose Capabilities from.
*/
public CapabilitiesFilterDialog() {
super();
m_Self = this;
initGUI();
}
/**
* sets up the GUI.
*/
protected void initGUI() {
JPanel panel;
CheckBoxListModel model;
setTitle("Filtering Capabilities...");
setLayout(new BorderLayout());
panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
getContentPane().add(panel, BorderLayout.NORTH);
m_InfoLabel.setText(""
+ m_ClassType.getName().replaceAll(".*\\.", "") + "s"
+ " have to support at least the following capabilities
"
+ "(the ones highlighted " + GOETreeNode.NO_SUPPORT
+ " don't meet these requirements
"
+ "the ones highlighted " + GOETreeNode.MAYBE_SUPPORT + " possibly meet them):"
+ "");
panel.add(m_InfoLabel, BorderLayout.CENTER);
// list
getContentPane().add(new JScrollPane(m_List), BorderLayout.CENTER);
model = (CheckBoxListModel) m_List.getModel();
for (Capability cap : Capability.values()) {
model.addElement(cap);
}
// buttons
panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
getContentPane().add(panel, BorderLayout.SOUTH);
m_OkButton.setMnemonic('O');
m_OkButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateCapabilities();
if (m_CapabilitiesFilter == null) {
m_CapabilitiesFilter = new Capabilities(null);
}
m_CapabilitiesFilter.assign(m_Capabilities);
m_Self.setVisible(false);
showPopup();
}
});
panel.add(m_OkButton);
m_CancelButton.setMnemonic('C');
m_CancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
m_Self.setVisible(false);
showPopup();
}
});
panel.add(m_CancelButton);
pack();
}
/**
* transfers the Capabilities object to the JList.
*
* @see #m_Capabilities
* @see #m_List
*/
protected void updateList() {
CheckBoxListModel model;
model = (CheckBoxListModel) m_List.getModel();
for (Capability cap : Capability.values()) {
model.setChecked(model.indexOf(cap), m_Capabilities.handles(cap));
}
}
/**
* transfers the selected Capabilities from the JList to the Capabilities
* object.
*
* @see #m_Capabilities
* @see #m_List
*/
protected void updateCapabilities() {
CheckBoxListModel model;
model = (CheckBoxListModel) m_List.getModel();
for (Capability cap : Capability.values()) {
if (model.getChecked(model.indexOf(cap))) {
m_Capabilities.enable(cap);
} else {
m_Capabilities.disable(cap);
}
}
}
/**
* sets the initial capabilities.
*
* @param value the capabilities to use
*/
public void setCapabilities(Capabilities value) {
if (value != null) {
m_Capabilities.assign(value);
} else {
m_Capabilities = new Capabilities(null);
}
updateList();
}
/**
* returns the currently selected capabilities.
*
* @return the currently selected capabilities
*/
public Capabilities getCapabilities() {
return m_Capabilities;
}
/**
* sets the JPopupMenu to display again after closing the dialog.
*
* @param value the JPopupMenu to display again
*/
public void setPopup(JPopupMenu value) {
m_Popup = value;
}
/**
* returns the currently set JPopupMenu.
*
* @return the current JPopupMenu
*/
public JPopupMenu getPopup() {
return m_Popup;
}
/**
* if a JPopupMenu is set, it is displayed again. Displaying this dialog
* closes any JPopupMenu automatically.
*/
public void showPopup() {
if (getPopup() != null) {
getPopup().setVisible(true);
}
}
}
/**
* Creates a popup menu containing a tree that is aware of the screen
* dimensions.
*/
public class JTreePopupMenu extends JPopupMenu {
/** for serialization. */
static final long serialVersionUID = -3404546329655057387L;
/** the popup itself. */
private final JPopupMenu m_Self;
/** The tree. */
private final JTree m_tree;
/** The scroller. */
private final JScrollPane m_scroller;
/** The filter button in case of CapabilitiesHandlers. */
private final JButton m_FilterButton = new JButton("Filter...");
/** The remove filter button in case of CapabilitiesHandlers. */
private final JButton m_RemoveFilterButton = new JButton("Remove filter");
/** The button for closing the popup again. */
private final JButton m_CloseButton = new JButton("Close");
/**
* Constructs a new popup menu.
*
* @param tree the tree to put in the menu
*/
public JTreePopupMenu(JTree tree) {
m_Self = this;
setLayout(new BorderLayout());
JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
add(panel, BorderLayout.SOUTH);
if (ClassDiscovery.hasInterface(CapabilitiesHandler.class, m_ClassType)) {
// filter
m_FilterButton.setMnemonic('F');
m_FilterButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == m_FilterButton) {
CapabilitiesFilterDialog dialog = new CapabilitiesFilterDialog();
dialog.setCapabilities(m_CapabilitiesFilter);
dialog.setPopup(m_Self);
dialog.setVisible(true);
m_Support.firePropertyChange("", null, null);
repaint();
}
}
});
panel.add(m_FilterButton);
// remove
m_RemoveFilterButton.setMnemonic('R');
m_RemoveFilterButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == m_RemoveFilterButton) {
m_CapabilitiesFilter = null;
m_Support.firePropertyChange("", null, null);
repaint();
}
}
});
panel.add(m_RemoveFilterButton);
}
// close
m_CloseButton.setMnemonic('C');
m_CloseButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == m_CloseButton) {
m_Self.setVisible(false);
}
}
});
panel.add(m_CloseButton);
m_tree = tree;
JPanel treeView = new JPanel();
treeView.setLayout(new BorderLayout());
treeView.add(m_tree, BorderLayout.NORTH);
// make backgrounds look the same
treeView.setBackground(m_tree.getBackground());
m_scroller = new JScrollPane(treeView);
m_scroller.setPreferredSize(new Dimension(300, 400));
m_scroller.getVerticalScrollBar().setUnitIncrement(20);
add(m_scroller);
}
/**
* Displays the menu, making sure it will fit on the screen.
*
* @param invoker the component thast invoked the menu
* @param x the x location of the popup
* @param y the y location of the popup
*/
@Override
public void show(Component invoker, int x, int y) {
super.show(invoker, x, y);
// calculate available screen area for popup
java.awt.Point location = getLocationOnScreen();
java.awt.Dimension screenSize = getToolkit().getScreenSize();
int maxWidth = (int) (screenSize.getWidth() - location.getX());
int maxHeight = (int) (screenSize.getHeight() - location.getY());
// if the part of the popup goes off the screen then resize it
Dimension scrollerSize = m_scroller.getPreferredSize();
int height = (int) scrollerSize.getHeight();
int width = (int) scrollerSize.getWidth();
if (width > maxWidth) {
width = maxWidth;
}
if (height > maxHeight) {
height = maxHeight;
}
// commit any size changes
m_scroller.setPreferredSize(new Dimension(width, height));
revalidate();
pack();
}
}
/**
* Handles the GUI side of editing values.
*/
public class GOEPanel extends JPanel {
/** for serialization. */
static final long serialVersionUID = 3656028520876011335L;
/** The component that performs classifier customization. */
protected PropertySheetPanel m_ChildPropertySheet;
/** The name of the current class. */
protected JLabel m_ClassNameLabel;
/** Open object from disk. */
protected JButton m_OpenBut;
/** Save object to disk. */
protected JButton m_SaveBut;
/** ok button. */
protected JButton m_okBut;
/** cancel button. */
protected JButton m_cancelBut;
/** The filechooser for opening and saving object files. */
protected JFileChooser m_FileChooser;
/** Creates the GUI editor component. */
public GOEPanel() {
m_Backup = copyObject(m_Object);
m_ClassNameLabel = new JLabel("None");
m_ClassNameLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
m_ChildPropertySheet = new PropertySheetPanel();
m_ChildPropertySheet
.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
m_Support.firePropertyChange("", null, null);
}
});
m_OpenBut = new JButton("Open...");
m_OpenBut.setToolTipText("Load a configured object");
m_OpenBut.setEnabled(true);
m_OpenBut.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Object object = openObject();
if (object != null) {
// setValue takes care of: Making sure obj is of right type,
// and firing property change.
setValue(object);
// Need a second setValue to get property values filled in OK.
// Not sure why.
setValue(object);
}
}
});
m_SaveBut = new JButton("Save...");
m_SaveBut.setToolTipText("Save the current configured object");
m_SaveBut.setEnabled(true);
m_SaveBut.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
saveObject(m_Object);
}
});
m_okBut = new JButton("OK");
m_okBut.setEnabled(true);
m_okBut.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
m_ChildPropertySheet.closingOK();
m_Backup = copyObject(m_Object);
if ((getTopLevelAncestor() != null)
&& (getTopLevelAncestor() instanceof Window)) {
Window w = (Window) getTopLevelAncestor();
w.dispose();
}
}
});
m_cancelBut = new JButton("Cancel");
m_cancelBut.setEnabled(true);
m_cancelBut.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
m_ChildPropertySheet.closingCancel();
if (m_Backup != null) {
m_Object = copyObject(m_Backup);
// To fire property change
m_Support.firePropertyChange("", null, null);
m_ObjectNames = getClassesFromProperties();
updateObjectNames();
updateChildPropertySheet();
}
if ((getTopLevelAncestor() != null)
&& (getTopLevelAncestor() instanceof Window)) {
Window w = (Window) getTopLevelAncestor();
w.dispose();
}
}
});
setLayout(new BorderLayout());
if (m_canChangeClassInDialog) {
JButton chooseButton = createChooseClassButton();
JPanel top = new JPanel();
top.setLayout(new BorderLayout());
top.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
top.add(chooseButton, BorderLayout.WEST);
top.add(m_ClassNameLabel, BorderLayout.CENTER);
add(top, BorderLayout.NORTH);
} else {
add(m_ClassNameLabel, BorderLayout.NORTH);
}
add(m_ChildPropertySheet, BorderLayout.CENTER);
// Since we resize to the size of the property sheet, a scrollpane isn't
// typically needed
// add(new JScrollPane(m_ChildPropertySheet), BorderLayout.CENTER);
JPanel okcButs = new JPanel();
okcButs.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
okcButs.setLayout(new GridLayout(1, 4, 5, 5));
okcButs.add(m_OpenBut);
okcButs.add(m_SaveBut);
okcButs.add(m_okBut);
okcButs.add(m_cancelBut);
add(okcButs, BorderLayout.SOUTH);
if (m_ClassType != null) {
m_ObjectNames = getClassesFromProperties();
if (m_Object != null) {
updateObjectNames();
updateChildPropertySheet();
}
}
}
/**
* Enables/disables the cancel button.
*
* @param flag true to enable cancel button, false to disable it
*/
protected void setCancelButton(boolean flag) {
if (m_cancelBut != null) {
m_cancelBut.setEnabled(flag);
}
}
/**
* Opens an object from a file selected by the user.
*
* @return the loaded object, or null if the operation was cancelled
*/
protected Object openObject() {
if (m_FileChooser == null) {
createFileChooser();
}
int returnVal = m_FileChooser.showOpenDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File selected = m_FileChooser.getSelectedFile();
try {
ObjectInputStream oi = new ObjectInputStream(new BufferedInputStream(
new FileInputStream(selected)));
Object obj = oi.readObject();
oi.close();
if (!m_ClassType.isAssignableFrom(obj.getClass())) {
throw new Exception("Object not of type: " + m_ClassType.getName());
}
return obj;
} catch (Exception ex) {
JOptionPane.showMessageDialog(this, "Couldn't read object: "
+ selected.getName() + "\n" + ex.getMessage(), "Open object file",
JOptionPane.ERROR_MESSAGE);
}
}
return null;
}
/**
* Saves an object to a file selected by the user.
*
* @param object the object to save
*/
protected void saveObject(Object object) {
if (m_FileChooser == null) {
createFileChooser();
}
int returnVal = m_FileChooser.showSaveDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File sFile = m_FileChooser.getSelectedFile();
try {
ObjectOutputStream oo = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream(sFile)));
oo.writeObject(object);
oo.close();
} catch (Exception ex) {
JOptionPane.showMessageDialog(this, "Couldn't write to file: "
+ sFile.getName() + "\n" + ex.getMessage(), "Save object",
JOptionPane.ERROR_MESSAGE);
}
}
}
/**
* Creates the file chooser the user will use to save/load files with.
*/
protected void createFileChooser() {
m_FileChooser = new JFileChooser(new File(System.getProperty("user.dir")));
m_FileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
}
/**
* Makes a copy of an object using serialization.
*
* @param source the object to copy
* @return a copy of the source object
*/
protected Object copyObject(Object source) {
Object result = null;
try {
result = GenericObjectEditor.makeCopy(source);
setCancelButton(true);
} catch (Exception ex) {
setCancelButton(false);
Logger.log(weka.core.logging.Logger.Level.WARNING,
"GenericObjectEditor: Problem making backup object");
Logger.log(weka.core.logging.Logger.Level.WARNING, ex);
}
return result;
}
/**
* Allows customization of the action label on the dialog.
*
* @param newLabel the new string for the ok button
*/
public void setOkButtonText(String newLabel) {
m_okBut.setText(newLabel);
}
/**
* This is used to hook an action listener to the ok button.
*
* @param a The action listener.
*/
public void addOkListener(ActionListener a) {
m_okBut.addActionListener(a);
}
/**
* This is used to hook an action listener to the cancel button.
*
* @param a The action listener.
*/
public void addCancelListener(ActionListener a) {
m_cancelBut.addActionListener(a);
}
/**
* This is used to remove an action listener from the ok button.
*
* @param a The action listener
*/
public void removeOkListener(ActionListener a) {
m_okBut.removeActionListener(a);
}
/**
* This is used to remove an action listener from the cancel button.
*
* @param a The action listener
*/
public void removeCancelListener(ActionListener a) {
m_cancelBut.removeActionListener(a);
}
/**
* Updates the child property sheet, and creates if needed.
*/
public void updateChildPropertySheet() {
// Update the object name displayed
String className = "None";
if (m_Object != null) {
className = m_Object.getClass().getName();
}
m_ClassNameLabel.setText(className);
// Set the object as the target of the propertysheet
m_ChildPropertySheet.setTarget(m_Object);
// Adjust size of containing window if possible
if ((getTopLevelAncestor() != null)
&& (getTopLevelAncestor() instanceof Window)) {
((Window) getTopLevelAncestor()).pack();
}
}
}
/**
* Default constructor.
*/
public GenericObjectEditor() {
this(false);
}
/**
* Constructor that allows specifying whether it is possible to change the
* class within the editor dialog.
*
* @param canChangeClassInDialog whether the user can change the class
*/
public GenericObjectEditor(boolean canChangeClassInDialog) {
m_canChangeClassInDialog = canChangeClassInDialog;
m_History = new GenericObjectEditorHistory();
ToolTipManager.sharedInstance().setDismissDelay(7000);
}
/**
* registers all the editors in Weka.
*/
public static void registerEditors() {
Properties props;
Enumeration> enm;
String name;
String value;
if (m_EditorsRegistered) {
return;
}
Logger.log(weka.core.logging.Logger.Level.INFO,
"---Registering Weka Editors---");
m_EditorsRegistered = true;
// load properties
try {
props = Utils.readProperties(GUIEDITORS_PROPERTY_FILE);
} catch (Exception e) {
props = new Properties();
e.printStackTrace();
}
// show the tool tip?
m_ShowGlobalInfoToolTip = props.getProperty(
"ShowGlobalInfoToolTip", "true").equals("true");
enm = props.propertyNames();
while (enm.hasMoreElements()) {
name = enm.nextElement().toString();
value = props.getProperty(name, "");
registerEditor(name, value);
}
}
public static void registerEditor(String name, String value) {
Class> baseCls;
Class> cls;
try {
// array class?
if (name.endsWith("[]")) {
baseCls = Class.forName(name.substring(0, name.indexOf("[]")));
cls = Array.newInstance(baseCls, 1).getClass();
} else {
cls = Class.forName(name);
}
// register
PropertyEditorManager.registerEditor(cls, Class.forName(value));
} catch (Exception e) {
Logger.log(weka.core.logging.Logger.Level.WARNING, "Problem registering "
+ name + "/" + value + ": " + e);
}
}
/**
* Sets whether the user can change the class in the dialog.
*
* @param value if true then the user can change the class
*/
public void setCanChangeClassInDialog(boolean value) {
m_canChangeClassInDialog = value;
}
/**
* Returns whether the user can change the class in the dialog.
*
* @return true if the user can change the class
*/
public boolean getCanChangeClassInDialog() {
return m_canChangeClassInDialog;
}
/**
* Returns the backup object (may be null if there is no backup.
*
* @return the backup object
*/
public Object getBackup() {
return m_Backup;
}
/**
* returns the name of the root element of the given class name,
* null
if it doesn't contain the separator.
*
* @param clsname the full classname
* @param separator the separator
* @return string the root element
*/
protected static String getRootFromClass(String clsname, String separator) {
if (clsname.indexOf(separator) > -1) {
return clsname.substring(0, clsname.indexOf(separator));
} else {
return null;
}
}
/**
* parses the given string of classes separated by ", " and returns the a
* hashtable with as many entries as there are different root elements in the
* class names (the key is the root element). E.g. if there's only "weka." as
* the prefix for all classes the a hashtable of size 1 is returned. if NULL
* is the input, then NULL is also returned.
*
* @param classes the classnames to work on
* @return for each distinct root element in the classnames, one entry in the
* hashtable (with the root element as key)
*/
public static Hashtable sortClassesByRoot(String classes) {
Hashtable> roots;
Hashtable result;
Enumeration enm;
int i;
StringTokenizer tok;
String clsname;
Vector list;
HierarchyPropertyParser hpp;
String separator;
String root;
String tmpStr;
if (classes == null) {
return null;
}
roots = new Hashtable>();
hpp = new HierarchyPropertyParser();
separator = hpp.getSeperator();
// go over all classnames and store them in the hashtable, with the
// root element as the key
tok = new StringTokenizer(classes, ", ");
while (tok.hasMoreElements()) {
clsname = tok.nextToken();
root = getRootFromClass(clsname, separator);
if (root == null) {
continue;
}
// already stored?
if (!roots.containsKey(root)) {
list = new Vector();
roots.put(root, list);
} else {
list = roots.get(root);
}
list.add(clsname);
}
// build result
result = new Hashtable();
enm = roots.keys();
while (enm.hasMoreElements()) {
root = enm.nextElement();
list = roots.get(root);
tmpStr = "";
for (i = 0; i < list.size(); i++) {
if (i > 0) {
tmpStr += ",";
}
tmpStr += list.get(i);
}
result.put(root, tmpStr);
}
return result;
}
/**
* Called when the class of object being edited changes.
*
* @return the hashtable containing the HierarchyPropertyParsers for the root
* elements
*/
protected Hashtable getClassesFromProperties() {
Hashtable hpps = new Hashtable();
String className = m_ClassType.getName();
Set cls = PluginManager.getPluginNamesOfType(className);
if (cls == null) {
return hpps;
}
List toSort = new ArrayList(cls);
Collections.sort(toSort, new ClassDiscovery.StringCompare());
StringBuilder b = new StringBuilder();
for (String s : toSort) {
b.append(s).append(",");
}
String listS = b.substring(0, b.length() - 1);
// Hashtable typeOptions =
// sortClassesByRoot(EDITOR_PROPERTIES.getProperty(className));
Hashtable typeOptions = sortClassesByRoot(listS);
if (typeOptions == null) {
/*
* System.err.println("Warning: No configuration property found in\n" +
* PROPERTY_FILE + "\n" + "for " + className);
*/
} else {
try {
Enumeration enm = typeOptions.keys();
while (enm.hasMoreElements()) {
String root = enm.nextElement();
String typeOption = typeOptions.get(root);
HierarchyPropertyParser hpp = new HierarchyPropertyParser();
hpp.build(typeOption, ", ");
hpps.put(root, hpp);
}
} catch (Exception ex) {
Logger.log(weka.core.logging.Logger.Level.WARNING, "Invalid property: "
+ typeOptions);
}
}
return hpps;
}
/**
* Updates the list of selectable object names, adding any new names to the
* list.
*/
protected void updateObjectNames() {
if (m_ObjectNames == null) {
m_ObjectNames = getClassesFromProperties();
}
if (m_Object != null) {
String className = m_Object.getClass().getName();
String root = getRootFromClass(className,
new HierarchyPropertyParser().getSeperator());
HierarchyPropertyParser hpp = m_ObjectNames.get(root);
if (hpp != null) {
if (!hpp.contains(className)) {
hpp.add(className);
}
}
}
}
/**
* Sets whether the editor is "enabled", meaning that the current values will
* be painted.
*
* @param newVal a value of type 'boolean'
*/
public void setEnabled(boolean newVal) {
if (newVal != m_Enabled) {
m_Enabled = newVal;
}
}
/**
* Sets the class of values that can be edited.
*
* @param type a value of type 'Class'
*/
public void setClassType(Class> type) {
m_ClassType = type;
m_ObjectNames = getClassesFromProperties();
}
/**
* Sets the current object to be the default, taken as the first item in the
* chooser.
*/
public void setDefaultValue() {
if (m_ClassType == null) {
Logger.log(weka.core.logging.Logger.Level.WARNING,
"No ClassType set up for GenericObjectEditor!!");
return;
}
Hashtable hpps = getClassesFromProperties();
HierarchyPropertyParser hpp = null;
Enumeration enm = hpps.elements();
try {
while (enm.hasMoreElements()) {
hpp = enm.nextElement();
if (hpp.depth() > 0) {
hpp.goToRoot();
while (!hpp.isLeafReached()) {
hpp.goToChild(0);
}
String defaultValue = hpp.fullValue();
setValue(Class.forName(defaultValue).newInstance());
}
}
} catch (Exception ex) {
Logger.log(weka.core.logging.Logger.Level.WARNING,
"Problem loading the first class: " + hpp.fullValue());
ex.printStackTrace();
}
}
/**
* Sets the current Object. If the Object is in the Object chooser, this
* becomes the selected item (and added to the chooser if necessary).
*
* @param o an object that must be a Object.
*/
@Override
public void setValue(Object o) {
if (m_ClassType == null) {
Logger.log(weka.core.logging.Logger.Level.WARNING,
"No ClassType set up for GenericObjectEditor!!");
return;
}
if (!m_ClassType.isAssignableFrom(o.getClass())) {
Logger.log(weka.core.logging.Logger.Level.WARNING,
"setValue object not of correct type!");
return;
}
setObject(o);
if (m_EditorComponent != null) {
m_EditorComponent.repaint();
}
updateObjectNames();
}
/**
* Sets the current Object.
*
* @param c a value of type 'Object'
*/
protected void setObject(Object c) {
// This should really call equals() for comparison.
boolean trueChange;
if (getValue() != null) {
trueChange = (!c.equals(getValue()));
} else {
trueChange = true;
}
m_Backup = m_Object;
m_Object = c;
if (m_EditorComponent != null) {
m_EditorComponent.updateChildPropertySheet();
}
if (trueChange) {
m_Support.firePropertyChange("", null, null);
}
}
/**
* Gets the current Object.
*
* @return the current Object
*/
@Override
public Object getValue() {
Object result = null;
try {
result = makeCopy(m_Object);
} catch (Exception ex) {
ex.printStackTrace();
}
return result;
}
/**
* Supposedly returns an initialization string to create a Object identical to
* the current one, including it's state, but this doesn't appear possible
* given that the initialization string isn't supposed to contain multiple
* statements.
*
* @return the java source code initialisation string
*/
@Override
public String getJavaInitializationString() {
return "new " + m_Object.getClass().getName() + "()";
}
/**
* Returns true to indicate that we can paint a representation of the Object.
*
* @return true
*/
@Override
public boolean isPaintable() {
return true;
}
/**
* Paints a representation of the current Object.
*
* @param gfx the graphics context to use
* @param box the area we are allowed to paint into
*/
@Override
public void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box) {
if (m_Enabled) {
String rep;
if (m_Object != null) {
if (m_Object instanceof CustomDisplayStringProvider) {
rep = ((CustomDisplayStringProvider) m_Object).toDisplay();
} else {
rep = m_Object.getClass().getName();
int dotPos = rep.lastIndexOf('.');
if (dotPos != -1) {
rep = rep.substring(dotPos + 1);
}
}
} else {
rep = "None";
}
java.awt.Font originalFont = gfx.getFont();
gfx.setFont(originalFont.deriveFont(java.awt.Font.BOLD));
FontMetrics fm = gfx.getFontMetrics();
int vpad = (box.height - fm.getHeight());
gfx.drawString(rep, 2, fm.getAscent() + vpad);
int repwidth = fm.stringWidth(rep);
gfx.setFont(originalFont);
if ((m_Object instanceof OptionHandler)
&& !(m_Object instanceof CustomDisplayStringProvider)) {
gfx.drawString(
" " + Utils.joinOptions(((OptionHandler) m_Object).getOptions()),
repwidth + 2, fm.getAscent() + vpad);
}
}
}
/**
* Returns null as we don't support getting/setting values as text.
*
* @return null
*/
@Override
public String getAsText() {
return null;
}
/**
* Returns null as we don't support getting/setting values as text.
*
* @param text the text value
* @throws IllegalArgumentException as we don't support getting/setting values
* as text.
*/
@Override
public void setAsText(String text) {
throw new IllegalArgumentException(text);
}
/**
* Returns null as we don't support getting values as tags.
*
* @return null
*/
@Override
public String[] getTags() {
return null;
}
/**
* Returns true because we do support a custom editor.
*
* @return true
*/
@Override
public boolean supportsCustomEditor() {
return true;
}
/**
* Returns the array editing component.
*
* @return a value of type 'java.awt.Component'
*/
@Override
public java.awt.Component getCustomEditor() {
if (m_EditorComponent == null) {
m_EditorComponent = new GOEPanel();
}
return m_EditorComponent;
}
/**
* Adds a PropertyChangeListener who will be notified of value changes.
*
* @param l a value of type 'PropertyChangeListener'
*/
@Override
public void addPropertyChangeListener(PropertyChangeListener l) {
m_Support.addPropertyChangeListener(l);
}
/**
* Removes a PropertyChangeListener.
*
* @param l a value of type 'PropertyChangeListener'
*/
@Override
public void removePropertyChangeListener(PropertyChangeListener l) {
m_Support.removePropertyChangeListener(l);
}
/**
* Gets the custom panel used for editing the object.
*
* @return the panel
*/
@Override
public JPanel getCustomPanel() {
final JButton chooseButton = createChooseClassButton();
m_ObjectPropertyPanel = new PropertyPanel(this, true);
JPanel customPanel = new JPanel() {
/** ID added to avoid warning */
private static final long serialVersionUID = 1024049543672124980L;
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
chooseButton.setEnabled(enabled);
}
};
customPanel.setLayout(new BorderLayout());
customPanel.add(chooseButton, BorderLayout.WEST);
customPanel.add(m_ObjectPropertyPanel, BorderLayout.CENTER);
return customPanel;
}
/**
* Creates a button that when clicked will enable the user to change the class
* of the object being edited.
*
* @return the choose button
*/
protected JButton createChooseClassButton() {
JButton setButton = new JButton("Choose");
// anonymous action listener shows a JTree popup and allows the user
// to choose the class they want
setButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JPopupMenu popup = getChooseClassPopupMenu();
// show the popup where the source component is
if (e.getSource() instanceof Component) {
Component comp = (Component) e.getSource();
popup.show(comp, comp.getX(), comp.getY());
popup.pack();
popup.repaint();
}
}
});
return setButton;
}
/**
* creates a classname from the given path.
*
* @param path the path to generate the classname from
* @return the generated classname
*/
protected String getClassnameFromPath(TreePath path) {
StringBuffer classname = new StringBuffer();
// recreate class name from path
int start = 0;
if (m_ObjectNames.size() > 1) {
start = 1;
}
for (int i = start; i < path.getPathCount(); i++) {
if (i > start) {
classname.append(".");
}
classname.append((String) ((GOETreeNode) path.getPathComponent(i))
.getUserObject());
}
return classname.toString();
}
/**
* Returns a popup menu that allows the user to change the class of object.
*
* @return a JPopupMenu that when shown will let the user choose the class
*/
public JPopupMenu getChooseClassPopupMenu() {
updateObjectNames();
// create the tree, and find the path to the current class
m_treeNodeOfCurrentObject = null;
final JTree tree = createTree(m_ObjectNames);
if (m_treeNodeOfCurrentObject != null) {
tree.setSelectionPath(new TreePath(m_treeNodeOfCurrentObject.getPath()));
}
tree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
// create the popup
final JPopupMenu popup = new JTreePopupMenu(tree);
// respond when the user chooses a class
tree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
GOETreeNode node = (GOETreeNode) tree.getLastSelectedPathComponent();
if (node == null) {
return;
}
if (node.isLeaf()) {
classSelected(getClassnameFromPath(tree.getSelectionPath()));
popup.setVisible(false);
}
}
});
return popup;
}
/**
* Creates a JTree from an object heirarchy.
*
* @param hpps the hierarchy of objects to mirror in the tree
* @return a JTree representation of the hierarchy
*/
protected JTree createTree(Hashtable hpps) {
GOETreeNode superRoot;
Enumeration enm;
HierarchyPropertyParser hpp;
if (hpps.size() > 1) {
superRoot = new GOETreeNode("root");
} else {
superRoot = null;
}
enm = hpps.elements();
while (enm.hasMoreElements()) {
hpp = enm.nextElement();
hpp.goToRoot();
GOETreeNode root = new GOETreeNode(hpp.getValue());
addChildrenToTree(root, hpp);
if (superRoot == null) {
superRoot = root;
} else {
superRoot.add(root);
}
}
JTree tree = new JTree(superRoot) {
/** For serialization */
private static final long serialVersionUID = 6991903188102450549L;
@Override
public String getToolTipText(MouseEvent e) {
if ((getRowForLocation(e.getX(), e.getY())) == -1) {
return null;
}
TreePath currPath = getPathForLocation(e.getX(), e.getY());
if (currPath.getLastPathComponent() instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) currPath
.getLastPathComponent();
if (node.isLeaf()) {
return ((GOETreeNode) node).getToolTipText();
}
}
return null;
}
};
tree.setToolTipText("");
return tree;
}
/**
* Recursively builds a JTree from an object heirarchy. Also updates
* m_treeNodeOfCurrentObject if the current object is discovered during
* creation.
*
* @param tree the root of the tree to add children to
* @param hpp the hierarchy of objects to mirror in the tree
*/
protected void addChildrenToTree(GOETreeNode tree, HierarchyPropertyParser hpp) {
try {
for (int i = 0; i < hpp.numChildren(); i++) {
hpp.goToChild(i);
GOETreeNode child = new GOETreeNode(hpp.getValue());
if ((m_Object != null)
&& m_Object.getClass().getName().equals(hpp.fullValue())) {
m_treeNodeOfCurrentObject = child;
}
tree.add(child);
if (hpp.isLeafReached() && m_ShowGlobalInfoToolTip) {
String algName = hpp.fullValue();
try {
Object alg = Class.forName(algName).newInstance();
String toolTip = Utils.getGlobalInfo(alg, true);
if (toolTip != null) {
child.setToolTipText(toolTip);
}
} catch (Exception ex) {
}
}
addChildrenToTree(child, hpp);
hpp.goToParent();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Called when the user selects an class type to change to.
*
* @param className the name of the class that was selected
*/
protected void classSelected(String className) {
try {
if ((m_Object != null) && m_Object.getClass().getName().equals(className)) {
return;
}
setValue(Class.forName(className).newInstance());
// m_ObjectPropertyPanel.showPropertyDialog();
if (m_EditorComponent != null) {
m_EditorComponent.updateChildPropertySheet();
}
} catch (Exception ex) {
JOptionPane.showMessageDialog(null, "Could not create an example of\n"
+ className + "\n" + "from the current classpath", "Class load failed",
JOptionPane.ERROR_MESSAGE);
ex.printStackTrace();
try {
if (m_Backup != null) {
setValue(m_Backup);
} else {
setDefaultValue();
}
} catch (Exception e) {
Logger.log(weka.core.logging.Logger.Level.WARNING, ex.getMessage());
ex.printStackTrace();
}
}
}
/**
* Sets the capabilities to use for filtering.
*
* @param value the object to get the filter capabilities from
*/
public void setCapabilitiesFilter(Capabilities value) {
m_CapabilitiesFilter = new Capabilities(null);
m_CapabilitiesFilter.assign(value);
}
/**
* Returns the current Capabilities filter, can be null.
*
* @return the current Capabiliities used for filtering
*/
public Capabilities getCapabilitiesFilter() {
return m_CapabilitiesFilter;
}
/**
* Removes the current Capabilities filter.
*/
public void removeCapabilitiesFilter() {
m_CapabilitiesFilter = null;
}
/**
* Makes a copy of an object using serialization.
*
* @param source the object to copy
* @return a copy of the source object
* @exception Exception if the copy fails
*/
public static Object makeCopy(Object source) throws Exception {
SerializedObject so = new SerializedObject(source);
Object result = so.getObject();
return result;
}
/**
* Returns the available classnames for a certain property in the props file.
*
* @param property the property to get the classnames for
* @return the classnames
*/
public static Vector getClassnames(String property) {
Vector result;
Set r = PluginManager.getPluginNamesOfType(property);
result = new Vector();
if (r != null) {
result.addAll(r);
}
Collections.sort(result, new ClassDiscovery.StringCompare());
return result;
}
/**
* Returns the history of the used setups.
*
* @return the history
*/
public GenericObjectEditorHistory getHistory() {
return m_History;
}
/**
* Tests out the Object editor from the command line.
*
* @param args may contain the class name of a Object to edit
*/
public static void main(String[] args) {
try {
GenericObjectEditor.registerEditors();
GenericObjectEditor ce = new GenericObjectEditor(true);
ce.setClassType(weka.classifiers.Classifier.class);
Object initial = new weka.classifiers.rules.ZeroR();
if (args.length > 0) {
ce.setClassType(Class.forName(args[0]));
if (args.length > 1) {
initial = Class.forName(args[1]).newInstance();
ce.setValue(initial);
} else {
ce.setDefaultValue();
}
} else {
ce.setValue(initial);
}
PropertyDialog pd = new PropertyDialog((Frame) null, ce, 100, 100);
pd.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
PropertyEditor pe = ((PropertyDialog) e.getSource()).getEditor();
Object c = pe.getValue();
String options = "";
if (c instanceof OptionHandler) {
options = Utils.joinOptions(((OptionHandler) c).getOptions());
}
System.out.println(c.getClass().getName() + " " + options);
System.exit(0);
}
});
pd.setVisible(true);
} catch (Exception ex) {
ex.printStackTrace();
System.err.println(ex.getMessage());
}
}
}