Wednesday, July 28, 2010

Auto-Alignment of JXTable TableHeaders

As a followup of the previous post, here's example code to automatically align the headers of a JXTable with the data underneath, using the new fangled SwingX renderers.

import org.jdesktop.swingx.renderer.DefaultTableRenderer;

public class AutoAlignedHeaderRenderer extends DefaultTableRenderer {
  
  // constructors etc. omitted  ...

  @Override
  public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean focused, int row, int column) {
   
    TableCellRenderer dataRenderer = table.getCellRenderer(row, column);

    int align = SwingConstants.LEFT;  // default to left

    if (dataRenderer instanceof JLabel) { // backwards compatibility
      align = ((JLabel)dataRenderer).getHorizontalAlignment();
    }
    else if (dataRenderer instanceof AbstractRenderer) {
      AbstractRenderer ar = (AbstractRenderer)dataRenderer;      
      align = ar.getComponentProvider().getHorizontalAlignment();
    }
    
    componentController.setHorizontalAlignment(align);

    return super.getTableCellRendererComponent(table, value, selected, focused, row, column);

  }

}

Friday, May 7, 2010

Auto-Alignment of JTable TableHeaders

Most Java programmers are familiar with setting up various TableCellRenderers for the contents of JTables.  Often the JTable.getDefaultRenderer() defaults are fine, if you set the proper class for each column.  Else you create your own class, typically subclassing JLabel or DefaultTableCellRenderer.  Numeric data is typically right aligned/justified, perhaps with a fixed number of decimal points, so that the digits line up nicely.  Text, boolean, and graphical data are more often left or center aligned.  In all, this entails some work, but the time spent seems to be justified

Ideally, the table headers, which are usually just the names of each column, should also be aligned to match their data.  You can painstakingly set up a custom renderer for each TableColumn, via something like myTable.getColumn().setHeaderRenderer().  For the common case where it is just a text label, this is tedium and overkill.  One could write some utility method to help out.

When recently confronted with this problem, I had a brainstorm that worked easily and simply for my client's code.  I had already painstaking setup the TableCellRenderers for each column.  Instead of duplicating that effort, why not just ask them?  The code looks like this:

public Component getTableCellRendererComponent(JTable table, Object value,
boolean selected, boolean focused, int row, int column)
{
// get the existing JLabel used for a renderer, for example
  JLabel jl = (JLabel) super.getTableCellRendererComponent(table, value, selected, focused, row, column);

// get the corresponding data renderer
  TableCellRenderer dr = table.getCellRenderer(row, column);
  if (dr instanceof JLabel)
    jl.setHorizontalAlignment(((JLabel)dr).getHorizontalAlignment());
  else
    jl.setHorizontalAlignment(SwingConstants.LEFT);  // or whatever works for a default for you

... // more code removed, 
  return jl;
}
}


You might worry that all the sets of HorizontalAlignment are slow and will fire a lot of property change events, but, assuming you are using a subclass of DefaultTableCellRenderer, they won't.  And ain't modern fast CPUs great?

Anyway, hoping this will help one of your projects.  I'm less familiar with the newfangled SwingX Highlighters, but seems like this would be a good place for them

Friday, April 9, 2010

Fixing the ClassCastException in LayoutComparator.compare()

My client's code was sporadically afflicted with a ClassCastException.  As you can see from the stack trace below, there is no client code involved, just Swing (and swingx) code

java.lang.ClassCastException
at javax.swing.LayoutComparator.compare(LayoutComparator.java:61)
at java.util.Arrays.mergeSort(Arrays.java:1293)
at java.util.Arrays.mergeSort(Arrays.java:1282)
at java.util.Arrays.sort(Arrays.java:1210)
at java.util.Collections.sort(Collections.java:159)
at javax.swing.SortingFocusTraversalPolicy.enumerateAndSortCycle(SortingFocusTraversalPolicy.java:119)
at javax.swing.SortingFocusTraversalPolicy.getFirstComponent(SortingFocusTraversalPolicy.java:434)
at javax.swing.LayoutFocusTraversalPolicy.getFirstComponent(LayoutFocusTraversalPolicy.java:148
at javax.swing.SortingFocusTraversalPolicy.getDefaultComponent(SortingFocusTraversalPolicy.java:511)
at java.awt.FocusTraversalPolicy.getInitialComponent(FocusTraversalPolicy.java:152)
at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:340)
at java.awt.Component.dispatchEventImpl(Component.java:4502)
at java.awt.Container.dispatchEventImpl(Container.java:2099)
at java.awt.Window.dispatchEventImpl(Window.java:2475)
at java.awt.Component.dispatchEvent(Component.java:4460)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
at java.awt.SequencedEvent.dispatch(SequencedEvent.java:101)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)

Google searches revealed that others have seen this error, and supposedly it is caused by Swing components being added or removed while not on the Event Dispatch Thread.  O.K., makes some sense.  But how can you determine which rogue component is causing the problem?  Simple-minded attempts to "hook-into" this stack trace to catch and view the cause are rebuffed by the fact that Component.dispatchEvent()  is final, and all of the dispatchEventImpl()s are package access, not protected.

The place you can access a FocusTraversalPolicy() is in your Container.  However, you cannot access the Comparator it uses, because getComparator() is protected.  So, you have to write your own TraversalPolicy.  The first part is easy.

public class BetterFocusTraversalPolicy extends LayoutFocusTraversalPolicy {

   public BetterFocusTraversalPolicy() {
      setComparator(new BetterLayoutComparator());
   }
}

So, what to use for the "better" Comparator?  I copied and pasted from javax.swing.LayoutComparator, changing a couple of lines.  Here is the original:

public int compare(Object o1, Object o2) {
... some code deleted ...
if (a == null) {
   // 'a' is not part of a Window hierarchy. Can't cope.
   throw new ClassCastException();
}

My first reaction is "why throw a ClassCastException?  (instead of say, an IllegalArgumentException.  But the deeper issue is that the code provides no information about the rogue Component that is not part of the hierarchy.  The proper behavior would be to put o1.toString() into the exception:, e.g.

public int compare(Object o1, Object o2) {
... some code deleted ...
if (a == null) {
   // 'a' is not part of a Window hierarchy. Can't cope.
   throw new ClassCastException(o1.toString());
}

Making this change quickly revealed the problem. One of the axes for a graph was Time, and it was subject to programmatic change in the data collection thread. Correcting this fixed the bug.
But it would sure be simpler if LayoutComparator provided the information in the first place.

Tuesday, March 9, 2010

Yet more JXTreeTable Knowledge - autoselecting a row after construction

This JXTreeTable knowledge - I'm just full of it.  :-)

My current client wants to read persisted data into a JXTreeTable, nothing special there, but then wants to automatically select the root node, which, in their application, would usually save the user a click, plus it enables all the cool gizmos for beginners to see.  My simple brute force approach was to read the data, set that into the model, and call

setRowSelectionInterval(0,0);

It wasn't working,  I could occasionally see the first row briefly highlight, but then it would go away, with no selection on the table, no cool enabled gizmos.  Drat.  After wasting time on futile efforts to set the selection later or more often, I eventually set breakpoints in setRowSelectionInterval() and clearSelection() to see who else was calling them.


The JXTreeTable constructor indirectly calls clearSelection().  It actually happens twice, through setModel() and setRowSorter().  But these happen during construction, well before my call, and are completely expected.

Somewhere after I read in the data, model.setRoot() gets called.  This triggers TreeTableMode.setColumnIdentifiers, which calls
TreeModelSupport.fireNewRoot(), which calls
TreeModelSupport.fireTreeStructureChanged(), which broadcasts to all the TreeModelListeners.

Other than sounding like all the "begats" in Genesis, this still makes sense.  One of the listeners is JXTreeTable's internal TreeModelListener, with a method, treeStructureChanged().  This method calls delayedFireTableStructureChanged() 

Now we are getting somewhere, that "delayed" should be a big hint. The code does the fairly obvious:

   SwingUtilities.invokeLater(new Runnable() {
      public void run() {
         fireTableStructureChanged();
      }
   });


Eventually, fireTableStructureChanged() will call JTable.clearSelectionAndLeadAnchor(), which, duh, calls clearSelection().

But, since this is called via invokeLater, it ends up happening after my call to select row 0.  Now, sharp purists would, at this time, point out that it was a mistake all along for me to select a row on a UI from a non-AWT thread.  But, but...  Anyway, the simple fix was, in my code, to also delay the selection.  After reading in the data and setting it into the model, instead of directly calling setRowSelectionInterval(0,0), wrap that call inside an invokeLater() i.e.

SwingUtilities.invokeLater(new Runnable() {
   public void run() {
      myTable.setRowSelectionInterval(0,0);
   } 
});


Now it works.  So, if you are struggling with selecting a row after setting up a JXTreeTable, now you know the trick too.

Wednesday, February 3, 2010

Stupid JXTreeTable Tricks

For one project for one of my clients, I've been using the new SwingX JXTreeTable. It works. Not sure if it is all that much better than some of the earlier code you'll find on the web, but it basically works. And I never used tree tables much before, so, being "new" is not an issue.  Now, it's a bit offputting to look at the source code and see two data model adapter classes and four TreeTableHacker classes, most of which have comments like:

     * Temporary class to have all the hacking at one place. Naturally, it will
     * change a lot. The base class has the "stable" behaviour as of around
     * jun2006 (before starting the fix for 332-swingx).

or

     * Note: currently this class looks a bit funny

or, my favorite

     * A more (or less, depending in pov :-) aggressiv hacker.

Anyway, I found a few weird bugs or features that cost me some hours and were easily fixed.

1)  JXTreeTable ignores your attempts to resize the columns.  You can resize them, but as soon as any of the table data changes, it takes over like a bad episode of Twilight Zone and ignores your settings.  After an hour of debugging to convince myself that my code to resize the columns was really working, and trying every possible combination of  setMinWidth(), setWidth, and/or setPreferredWidth(), a little Googleing found this thread.  When changing the content of the table, JXTreetable always recreates the columns, even in the most common case where the number of columns hasn't changed.  Silly.  Anyway, you can complain about their design. or you can simply add this line of code somewhere:

setAutoCreateColumnsFromModel(false);

2) JXTreeTable messes with the colors of your Renderers.  If you are used to "traditional" JTable Renderers, you are in for a surprise as JXTreetable will, by default, apply one of their newfangled Highlighters to your renderer.  (For the record, Highlighters look like a good idea, but forcing them on you isn't)  Here is part of a comment from JXTable source:

     * Client code which solves the problem at the core (that is in a
     * well-behaved DefaultTableCellRenderer) can disable the hack
     * by removing the client property or by subclassing and override this
     * to do nothing.

So, lets review.  Well-behaved code must go out of it's way to avoid getting broken, while poorly written code is automatically "fixed".  Seems bass-ackwards to me.  I guess I have to start writing lousy, ill-behaved renderers.  :-)

Anyway, you can get around this issue by adding this line to your code:

putClientProperty(USE_DTCR_COLORMEMORY_HACK, null);

(the "null" could also be Boolean.FALSE as well).

Hope this helps and save you some time.

Wednesday, December 30, 2009

Answering the Clojure vs. Ruby & Scala Challenge

Lau Jensen has done an interesting comparison of the relatively "new" languages Clojure, Ruby & Scala, comparing them for lines of code and speed on a sample "interview" problem, counting unique words in a directory.  Below in my Java version of the solution.  (You can also find it here)  One could squeeze a few more LOC out of it, but, IMO, that's not the point. The point is to show that a relatively normal and "readable" Java program, using no foreign libraries, does the same job in a reasonably similar amount of lines of code to the others.  I think you could find some Apache or similar code to do some of this work.

The original code is verbose "thanks" to all the generics definitions.


Note added after first posting:

You can save considerable time (about 15%) by pre-compiling the regular expression.  Add a line

Pattern splitOnWhitespace = Pattern.compile("[ \t]");

then change line 18, the line.split() code to

for (String s : splitOnWhitespace.split(line))

I've done this below, plus fixed one other issue about the definition of a "word".

I haven't does a full comparison timing, but hopefully Lau will run one shortly.

import java.io.*;
import java.util.*;

public class WordCounter {

 public static void main(String[] args) throws IOException {
  Long timeStart = System.currentTimeMillis();
  File rootDir = new File("C:/temp/20_newsgroups");
  CountingSet counter = new CountingSet();
  Pattern wordPattern = Pattern.compile("\\w+");

  for (File groupDirectory : rootDir.listFiles())
   if (groupDirectory.isDirectory())
    for (File f : groupDirectory.listFiles()) {
     if (f.isFile()) {
      BufferedReader reader = new BufferedReader(new FileReader(f));
      String line;
      while ((line = reader.readLine()) != null) {
       Matcher matcher = wordPattern.matcher(line);
       while (matcher.find())
        counter.add(matcher.group());
      }
      reader.close();
     }
    }

   PrintWriter pw = new PrintWriter("C:/temp/counts-alphabetical-java.txt");
   for (Map.Entry<String, Integer> me : counter.entrySet())
    pw.println(me.getKey() + " : " + me.getValue());
   pw.close();
   pw = new PrintWriter("C:/temp/counts-decreasing-java.txt");
   spewInverted(counter, pw);
   pw.close();
   System.out.println("Finished in " + 0.001
            * (System.currentTimeMillis() - timeStart) + " seconds");
   }

 static void spewInverted(Map<String, Integer> in, PrintWriter pw) {
  ArrayList<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(
            in.entrySet());
  Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
   public int compare(Map.Entry<String, Integer> o1,
Map.Entry<String, Integer> o2) {
    return o2.getValue().compareTo(o1.getValue());
   }
  });

  for (Map.Entry<String, Integer> entry : list)
   pw.println(entry.getKey() + " : " + entry.getValue());
 }

}

class CountingSet extends TreeMap<String, Integer> {
 void add(String s) {
  Integer i = get(s);
  put(s, (i== null) ? Integer.valueOf(1) : Integer.valueOf(i+1));
 }
}


Monday, December 28, 2009

Java Event Handling part 3: Double Dispatch

In the past two blogs, I presented an alternative route to Java event handling. Here's a final twist allowing one to easily listen to multiple types of events. It works best when you are in control of the events, i.e., they are "business logic" under your control. The technique is called "double dispatch".

1) Define a base class for your events. In my example code, you are running a email spam business, so there is a SpamEvent, with several (internal) subclasses.

2) Define an interface, e.g. SpamListener, with calls to listen to every subclass of event. Note that the names of the calls need not be distinctive, since the signature of the argument varies. For example, the name of the call could still be simply handleEvent().

3) Each subclass of the base event should implement a method
public void doubleDispatch(SpamListener listener) {
listener.handleEvent(this);
}

4) You need to implement a genericized listener (as in my previous two posts) for the base class, which calls doubleDispatch, e.g.


public void handleEvent(SpamEvent event) {
event.doubleDispatch(theSpamListener);
}

What happens is that this one listener for all of your events, tells the event to double dispatch. The event knows what type it is, and calls the appropriate method in theSpamListener.

Example source code (with JUnit tests) for this is available at my wiki:

http://flyingspaniel.wikidot.com/java-event-handling