Skip to main content

A simple class browser

Posted by richunger on May 25, 2006 at 11:26 PM PDT

At JavaOne, Geertjan gave a talk on building an IDE for a specific framework (Wicket). I work on a similar project for an in-house framework. One feature that I thought would be useful is a convenient Java Class selector.

I really like the "Fast Open" selector that you get from the "Navigate...Go To Class" menu item. I looked at the code, and was dismayed to see that it was hard-coded for opening classes, and was not reusable for just selecting a class for another purpose (such as inserting a class reference in an xml file).

So, I sat down to write such a widget, and was pleased to be able to put something pretty featureful together in a couple of hours. I use the MDR API for querying classes.

classbrowser.jpg

The list box contains all classes whose classname (without package) starts with the substring in the text field. The list selection is the value returned by the dialog.

Here's a little tour of the code:

public class ClassSelector extends javax.swing.JDialog
{
    // the data for the ListModel
    private List candidates = new ArrayList();

    // an example usage
    public static String getFullyQualifiedClassName()
    {
        ClassSelector cs = new ClassSelector();
        cs.setVisible(true);
        JCClass c = cs.getSelection();
        return c == null ? null : c.getFullName();
    }

    public ClassSelector()
    {
        this(WindowManager.getDefault().getMainWindow(), true);
    }

    public ClassSelector(java.awt.Frame parent, boolean modal)
    {
        super(parent, modal);
        initComponents();
    }

    public JCClass getSelection()
    {
        return (JCClass)matchingList.getSelectedValue();
    }

    private void initComponents()
    {
        // matisse-generated code here
    }

    private void matchingListKeyPressed(java.awt.event.KeyEvent evt)
    {
        // make ENTER work the same as the OK button
        if (evt.getKeyCode() == KeyEvent.VK_ENTER)
            okButtonActionPerformed(null);
    }

    private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt)
    {
        matchingList.setSelectedIndex(-1);
        dispose();
    }

    private void okButtonActionPerformed(java.awt.event.ActionEvent evt)
    {
        dispose();
    }

    private ListModel createListModel()
    {
        return new Model();
    }

    private ListCellRenderer createCellRenderer()
    {
        return new Renderer();
    }

    private class Model extends KeyAdapter implements ListModel
    {
        private List listeners = new ArrayList();

        public Model()
        {
            classNameField.addKeyListener(this);
        }

        public int getSize()
        {
            return candidates.size();
        }

        public Object getElementAt(int index)
        {
            return candidates.get(index);
        }

        public void addListDataListener(ListDataListener l)
        {
            listeners.add(l);
        }

        public void removeListDataListener(ListDataListener l)
        {
            listeners.remove(l);
        }

        private void fireListChange()
        {
            ListDataEvent evt = new ListDataEvent(this,
                    ListDataEvent.CONTENTS_CHANGED, 0, getSize()-1);
            for (ListDataListener l : listeners)
            {
                l.contentsChanged(evt);
            }
        }

        private void refresh()
        {
            String str = classNameField.getText();

            if (str == null || str.trim().length() == 0)
            {
                // text field is blank, so don't do a query
                if (!candidates.isEmpty())
                {
                    candidates.clear();
                    fireListChange();
                }
            }
            else
            {
                // clear out the old query results, and do a new query
                str = str.trim();
                candidates.clear();

                JCFinder finder = JCFinderFactory.getDefault().getGlobalFinder();

                // TODO: make case insensitive
                List classes = finder.findClasses(
                        null, // don't specify a package
                        str,
                        false); // don't require an exact match

                candidates.addAll(classes);

                fireListChange();

                if (!candidates.isEmpty() && matchingList.getSelectedIndex() == -1)
                {
                    // if there's no selection at this point, select the first item
                    matchingList.setSelectedIndex(0);
                }

            }
        }

        /** each time a letter is typed, do a new query */
        public void keyReleased(KeyEvent e)
        {
            refresh();
        }
    }

    private class Renderer implements ListCellRenderer
    {
        public Component getListCellRendererComponent(
                JList list, Object value, int index,
                boolean isSelected, boolean cellHasFocus)
        {
            JCClass c = (JCClass)value;
            String str = c.getName() + " (" + c.getPackageName() + ")";
            JLabel label = new JLabel(str);
            if (isSelected)
            {
                label.setBackground(Color.BLUE);
                label.setForeground(Color.WHITE);
                label.setOpaque(true);
            }
            return label;
        }
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton cancelButton;
    private javax.swing.JTextField classNameField;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JList matchingList;
    private javax.swing.JButton okButton;
    // End of variables declaration//GEN-END:variables

}

Neat, eh? The full code is here.

Related Topics >>