hacking on java 8 with netbeans 8

While waiting out the rain and tornado watches here in Orlando, I spent my time indoors hacking together a test framework to learn, yet again, how to drop tables (JTable) into tabbed panes (JTabbedPane) and provide a simple way to close individual tabs when needed. I decided to do all this Java hackery with NetBeans 8. NB8 was released at the same time as Java 8, and is meant to support the latest Java 8 features, including lambda expressions. Because this is research for a bigger project at work, and we’re still on Java 7 update 51, I had to forgo using all the latest features, especially lambda expressions.

I needed a framework to investigate (and frankly, re-learn) how to build tables, render data in them, and dismiss them. It’s been years since I had to do anything non-trivial with the Java Foundation Classes, and that had me scrambling through a few tutorials to come back up to speed. I knew what I wanted, just not quite how to achieve it.

For this first step, I wanted to:

  1. Create tabs with individual close buttons on each tab. You should be able to dismiss the tabs in any arbitrary order.
  2. Render independent tables on each tab.
  3. Alternately render row color, using the colors wheat2 and white (the default) in this case.

I pretty much achieved all those goals by the end of the day (in between doing all the other little Saturday chores that needed doing). What follows is the code I hacked together for this example. I will not claim this is the best example of Java programming, far from it. If you’re searching for the best idiomatic Java, then you should probably look elsewhere. My experiences with Java goes back to the mid-1990s and Java 1, with all that that implies.

As for NetBeans 8, for the most part it didn’t get in my way as I was hacking this prototype code together. It was fast and reasonably fluid, although I could have done with a bit less autocomplete. Right now I have three Java IDEs on my Windows notebook; Netbeans 8, IntelliJ IDE 13.1.1, and Eclipse. I’ve got Eclipse because of a lot of “legacy” Android projects I started there. I had every intention of moving out of Eclipse and into IntelliJ/Android Studio, but Android Studio is still under heavy development and I have real needs now, both in Android and regular Java. I’m not too happy with NetBeans 8 and its dropping Scala support, which it had in version 7.4 and earlier. IntelliJ has that support, for the latest Scala version, and I’m happy with that. IntelliJ 13.1.1 also support Java 8. If I had my way I’d migrate all my work to IntelliJ and step up from the Community edition to a fully paid-for IntelliJ license. Maybe in the near future.

I’m posting all this as much for my future benefit as for anyone else who’s interested. I don’t want to have to go scrambling to re-discover this again.

package tabbedtables;import java.awt.Color;import java.awt.Component;import java.awt.Dimension;import java.awt.GridLayout;import java.awt.event.KeyEvent;import javax.swing.ImageIcon;import javax.swing.JFrame;import javax.swing.JPanel;import javax.swing.JScrollPane;import javax.swing.JTabbedPane;import javax.swing.JTable;import javax.swing.SwingUtilities;import javax.swing.table.TableCellRenderer;public class TabbedTables {public static Object[][] sampleData ={{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },{ "sample", "sample", "sample", "sample" },};private static JPanel makeTable() {JTable table = new JTable(new TableModel(sampleData)) {@Overridepublic Component prepareRenderer(TableCellRenderer renderer, int row, int col) {Component component = super.prepareRenderer(renderer, row, col);//// Alternate row color//if (!isRowSelected(row))component.setBackground(row % 2 == 0 ? getBackground() : Color.decode("0xEED8AE"));return component;}};JPanel panel = new JPanel(new GridLayout(1,0));table.setPreferredScrollableViewportSize(new Dimension(800, 600));table.setFillsViewportHeight(true);panel.add(new JScrollPane(table));return panel;}private static ImageIcon createImageIcon(String path) {java.net.URL imageURL = TabbedTables.class.getResource(path);if (imageURL != null) return new ImageIcon(imageURL);return null;}private static void createGUI() {JTabbedPane tabbedPane = new JTabbedPane();ImageIcon icon = createImageIcon("images/image.gif");tabbedPane.addTab("Tab 1", icon, makeTable(), "Sample 1");tabbedPane.setTabComponentAt(0, new CloseTabControl(tabbedPane));tabbedPane.setMnemonicAt(0, KeyEvent.VK_1);tabbedPane.addTab("Tab 2", icon, makeTable(), "Sample 2");tabbedPane.setTabComponentAt(1, new CloseTabControl(tabbedPane));tabbedPane.setMnemonicAt(1, KeyEvent.VK_2);JFrame.setDefaultLookAndFeelDecorated(false);JFrame frame = new JFrame("Test tabs and tables");frame.setContentPane(tabbedPane);frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.setSize(800, 600);frame.setVisible(true);}public static void main(String[] args) {SwingUtilities.invokeLater(new Runnable() {@Overridepublic void run() {createGUI();}});}}
package tabbedtables;import java.awt.BasicStroke;import java.awt.Color;import java.awt.Dimension;import java.awt.FlowLayout;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.awt.event.MouseAdapter;import java.awt.event.MouseEvent;import java.awt.event.MouseListener;import javax.swing.AbstractButton;import javax.swing.BorderFactory;import javax.swing.JButton;import javax.swing.JLabel;import javax.swing.JPanel;import javax.swing.JTabbedPane;import javax.swing.plaf.basic.BasicButtonUI;public class CloseTabControl extends JPanel {private JTabbedPane parentTabbedPane;public CloseTabControl(final JTabbedPane pane) {super(new FlowLayout(FlowLayout.LEFT,0,0));if (pane == null) throw new NullPointerException("JTabbedPane is null");parentTabbedPane = pane;setOpaque(false);JLabel label = new JLabel() {@Overridepublic String getText() {int index = pane.indexOfTabComponent(CloseTabControl.this);if (index != -1) return pane.getTitleAt(index);return null;}};label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));add(label);add(new TabButton());setBorder(BorderFactory.createEmptyBorder(2,0,0,0));}// This is where the key work gets done. Create a button and then// associate mouse listeners to indicate when the mouse moves into and// out of a given instance, as well as close the associated tab when// mouse clicked.//private class TabButton extends JButton implements ActionListener {public TabButton() {initTabButton();}// initTabButton was created to squash the "leak this in constructor"// warning, when all of this was in the constructor.//private void initTabButton() {setPreferredSize(new Dimension(20, 20));setToolTipText("Close this tab.");setUI(new BasicButtonUI());setContentAreaFilled(false);setFocusable(false);setBorder(BorderFactory.createEtchedBorder());setBorderPainted(false);addMouseListener(buttonMouseListener);setRolloverEnabled(true);addActionListener(this);}// This is where the tab is closed when a mouse click event is// recieved.//@Overridepublic void actionPerformed(ActionEvent event) {int index = parentTabbedPane.indexOfTabComponent(CloseTabControl.this);if (index != -1) parentTabbedPane.remove(index);}@Overridepublic void updateUI() {}@Overrideprotected void paintComponent(Graphics graphics) {Graphics2D g2 = (Graphics2D) graphics.create();if (getModel().isPressed()) g2.translate(1,1);g2.setStroke(new BasicStroke(3));g2.setColor(Color.BLACK);if (getModel().isRollover()) g2.setColor(Color.RED);int tweek = 4;g2.drawLine(tweek, tweek, getWidth() - tweek - 1, getHeight() - tweek - 1);g2.drawLine(getWidth() - tweek - 1, tweek, tweek, getHeight() - tweek - 1);g2.dispose();}}private final static MouseListener buttonMouseListener = new MouseAdapter() {@Overridepublic void mouseEntered(MouseEvent event) {if (event.getComponent() instanceof AbstractButton) {((AbstractButton)event.getComponent()).setBorderPainted(true);}}@Overridepublic void mouseExited(MouseEvent event) {if (event.getComponent() instanceof AbstractButton) {((AbstractButton)event.getComponent()).setBorderPainted(false);}}};}
package tabbedtables;import javax.swing.table.AbstractTableModel;public class TableModel extends AbstractTableModel {private final String[] columnNames = {"Column 1", "Column 2", "Column 3", "Column 4"};private Object[][] data = null;public TableModel(Object[][] initialData) {data = initialData;}@Overridepublic int getColumnCount() {return columnNames.length;}@Overridepublic int getRowCount() {return data != null ? data.length : 0 ;}@Overridepublic String getColumnName(int column) {return columnNames[column];}@Overridepublic Object getValueAt(int row, int col) {return data != null ? data[row][col] : null ;}@Overridepublic Class getColumnClass(int col) {return getValueAt(0, col).getClass();}@Overridepublic boolean isCellEditable(int row, int col) {return false;}@Overridepublic void setValueAt(Object value, int row, int col) {if (data != null) {data[row][col] = value;fireTableCellUpdated(row, col);}}}

raspberry pi and java 8

What you’re looking at is the SwingSet2 demo, something rather old now, running via the latest Java release, Java 8 on my Raspberry Pi, on the twm desktop and on top of the latest Arch Linux for ARM. What a mouthful…

It’s been almost a month now since my last Raspberry Pi post; I was on a business trip in Kansas for three weeks. During that period of time Oracle released Java 8 on 18 March. One of the Java implementations was for ARM V6, compiled for hard float (chip-supported floating point math). As a test I also dropped the demo package on my RPi and fired up one of the demos. To be honest it was slow starting, but not horribly slow. It was slow like my computers from a good decade ago, the ones running early Pentium chips (back when Intel still called their chips Pentiums). I don’t intend to run graphical Java applications as much as service type applications.

I’m looking at Java 8 to run JavaScript via Java 8’s built-in JavaScript engine, Nashorn. The key reason is not to abandon node.js, but to shift the JavaScript portion (if possible) to run on Nashorn as apposed to Google’s V8. There are a number of reasons for doing this, one of which is security; I believe Java 8 has far more than V8. Before you bust a gasket telling me about Java’s security sins, know that the vector for Java security breaches was with older versions of Java using applets. That’s not what we’re about here. A second key reason is that I can call down into the Java libraries via JavaScript. And I can investigate manipulating the RPi’s various I/O devices via Java 8.

I don’t know when I’ll release a new image. Compressed, my 8GB images are now at 1GB, which makes it a chore to upload them, and I’m sure a chore for you to download. Although I’ve seen downloads from Sourceforge, they’ve pretty much dried up. I may make an image with Java 8 available on April 1, like I had originally intended. It’ll have Arch Linux fully up to date as well as Java 8.

For now tax season looms and other work is making my life very busy. I’ve got another two week business trip coming up, and more business travel after that. It’s a long busy stretch ahead.

Update


I installed Apache ant and built one of the sample applications, Scriptpad. In this simple example JavaScript is calling Java’s JFrame class and instantiating a running instance with a simple title. It took all of five lines of JavaScript to invoke a Java class instance. Not too shabby. This is, of course, just a trivial proof of concept. But it does illustrate the power of the JVM.