You are currently browsing the Philipp Meier's weblog blog archives for June, 2004

report


marketing

Show versions in Maven’s repository

Wondering what are the available versions for a certain library? Use the following shell script which displays the versions and if they’re available locally (l) or remote (r):

#!/bin/sh
REMOTE_REPO=http://www.ibiblio.org/maven
DIR="/shared/soft/java/maven-repository/$1"
echo "Available versions for $1:"
(
[ -d "$DIR" ] && ls $DIR/jars/*.jar | sed -e "s/.*\/$1-/$1-/;s/$/ (l)/";
curl -s $REMOTE_REPO/$1/jars/ 2>/dev/null |perl -ne'/HREF="(.*?\.jar)"/&& print $1,"(r)\n"'
) | sort | uniq

Example usage:

user@host:~> mversion quartz
Available versions for quartz:
quartz-1.0.2.jar(r)
quartz-1.0.7.jar(r)
quartz-1.0-b4.jar(r)
quartz-1.0.jar(r)
quartz-1.2.2.jar(r)
quartz-1.2.3.jar (l)
quartz-1.2.3.jar(r)
quartz-1.3.4.jar(r)
quartz-1.4.0.jar (l)
quartz-1.4.0.jar(r)
quartz-SNAPSHOT.jar(r)
e-mail

Lazy Table Model for Swing client server application

Fat Java clients that connect to remote servers for data retrieval offen suffer from the round-trip-time a remote server call has. This make caching of the loaded application data nessecary in most cases. The design I present shows how to implement a “data is loading” notification to the user.

To enable a reload of the data I introduce a small helper interface Updateable:

public interface Updateable {
    public void update();
}

public interface UpdateableTableModel extends TableModel, Updateable {
    /* Declare the following methods from AbstractTableModel to make
        them accessible from "outside" */
    public void fireTableStructureChanged();

    public void fireTableDataChanged();
}

We want a dummy for testing: here is a little TestTableModel which randomly changes on update and which intentionally takes some time to update (this simulates remote server access):

public class TestTableModel extends AbstractTableModel implements  UpdateableTableModel{
    Object[][]data;
    private Random random;

    public TestTableModel() {
        this.random = new Random();
    }

    public int getColumnCount() {
        if(data==null) {
            update();
        }
        return data[0].length;
    }

    public int getRowCount() {
        if(data==null) {
            update();
        }
        return data.length;
    }

    public Object getValueAt(int rowIndex, int columnIndex) {
        return data[rowIndex][columnIndex];
    }

    public  void update() {
        System.err.println("Updateing");
        int rows = random.nextInt(1000)+1;
        int cols = random.nextInt(10)+10;
        Object[][] newData = new Object[rows][cols];
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            //ignore
        }
        for(int r = 0; r < rows; r++) {
            for (int c=0; c < cols; c++) {
                newData[r][c] = new Integer(random.nextInt(1000));
            }
        }
        data=newData;
        System.err.println("...ended");
        // Using invokeLater because update could be triggered by
        // getCount or getRows which are called from the Swing Thread.
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                fireTableStructureChanged();
            }
        });

    }
}

If one uses TestTableModel directly in a JTable one will notice a freeze
of the swing interface on update. But instead of making the model itself running asynchronly we will mangle it with a litte java proxy magic.

I use the small but beautiful proxytoys package from thoughtworks to simplify proxy generation. However, the pattern can be implemented using plain java proxies, too.

Please do not get scared by the following implementation. The invoker's methode invoke is called by the proxy instead of the real models matching method.

class LazyTableModelInvoker implements Invoker {
    protected final Logger log = Logger.getLogger(getClass().getName());
    
    private Boolean updated = Boolean.FALSE;
    private Boolean updateing = Boolean.FALSE;
    private final UpdateableTableModel delegate;
    private static Method METHOD_GET_ROW_COUNT;
    private static Method METHOD_GET_VALUE_AT;
    private static Method METHOD_UPDATE;
    private static Method METHOD_GET_COL_COUNT;
    private static Method METHOD_GET_COL_NAME;

    private boolean fixedCols;
    private String LOADING_MESSAGE ="Loading";


    public LazyTableModelInvoker(UpdateableTableModel delegate, boolean fixedCols) {
        try {
            METHOD_GET_COL_COUNT = 
                 UpdateableTableModel.class.getMethod("getColumnCount", null);
            METHOD_GET_ROW_COUNT = 
                 UpdateableTableModel.class.getMethod("getRowCount", null);
            METHOD_GET_VALUE_AT = 
                 UpdateableTableModel.class.getMethod("getValueAt", 
                        new Class[]{Integer.TYPE, Integer.TYPE});
            METHOD_GET_COL_NAME = 
                 UpdateableTableModel.class.getMethod("getColumnName", 
                        new Class[]{Integer.TYPE});
            METHOD_UPDATE =
                 UpdateableTableModel.class.getMethod("update", null);
            this.delegate = delegate;
            this.fixedCols = fixedCols;
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Internal error", e);
        }
    }

    public Object invoke(Object proxy, Method method, Object[] args) 
        throws Throwable {
        if (method.equals(METHOD_UPDATE)) {
            triggerUpdate();
            return null;
        } else {
            if (!updated.booleanValue()) {
                triggerUpdate();
                return invokeLoading(method, args, delegate);
            } else {
                return method.invoke(delegate, args);
            }
        }
    }

    private void triggerUpdate() {
        synchronized (updated) {
            updated = Boolean.FALSE;

            synchronized (updateing) {
                if (updated.booleanValue() || updateing.booleanValue()) {
                    return;
                }
                log.finer("LTMI: Not updating");

                updateing = Boolean.TRUE;
                fireTableStructureChanged(); // Now different values during update
                new Thread("Lazy table model invoker: updating") {
                    public void run() {
                        log.finer("LTMI: Now updating");
                        try {
                            delegate.update();
                            log.finer("LTMI: Now updated!");

                        } finally {
                            fireTableStructureChanged();
                            updateing = Boolean.FALSE;
                            updated = Boolean.TRUE;
                            log.finer("LTMI: Not updating anymore.");
                        }
                    }
                }.start();
            }

        }


    }

    private void fireTableStructureChanged() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                if (fixedCols) {
                    delegate.fireTableDataChanged();
                } else {
                    delegate.fireTableStructureChanged();
                }
            }
        });
    }

    private Object invokeLoading(Method method, Object[] args, 
            final UpdateableTableModel delegate) throws NoSuchMethodException, 
                                      IllegalAccessException, InvocationTargetException {
        if (method.equals(METHOD_GET_ROW_COUNT)) {
            return new Integer(1);
        } else if (method.equals(METHOD_GET_COL_COUNT) && !fixedCols) {
            return new Integer(1);
        } else if (method.equals(METHOD_GET_VALUE_AT)) {
            return new Integer(0).equals(args[0]) ? LOADING_MESSAGE : "";
        } else if (method.equals(METHOD_GET_COL_NAME) && !fixedCols) {
            return LOADING_MESSAGE;
        } else {
            return method.invoke(delegate, args);
        }
    }

}

How does it work?

First of all, invoke checks if the method update was called and will trigger an asynchronus update of the real tabel model. If this is not the case it test if the model has been updated and will either hand on the invokation to the real model or instead intercept it and call invokeLoading.

lnvokeLoading simulates a 1 x 1 Table model which first and only column "Loading" has only one cell: "Loading".

The method triggerUpdate will spawn a new Thread wich run in the background and invokes the eventually long during update on the real model. Afterwards all TableModelListeners get informed that the table structure might have been changed and invokes fireTableStructureChanged.

Putting it all together


/**
  * Little factory class
  **/
public class LazyTableModel {
    public static UpdateableTableModel createLazyTableModel(
                        UpdateableTableModel testTableModel, boolean fixedCols) {
        return createLazyTableModel(new StandardProxyFactory(),
                                                  testTableModel, fixedCols);
    }
    public static UpdateableTableModel createLazyTableModel(
             ProxyFactory proxyFactory, UpdateableTableModel testTableModel,
             boolean fixedCols) {
        Class[] udtClass = new Class[]{UpdateableTableModel.class};
        return (UpdateableTableModel) proxyFactory.createProxy(
           udtClass, new LazyTableModelInvoker(testTableModel, fixedCols));
    }
}

public class LazyTableModelTest {
    public static void main(String[] args) {
        JFrame frame = new JFrame(LazyTableModel.class.getName());
        Container content = frame.getContentPane();
        content.setLayout(new BorderLayout());
        UpdateableTableModel testModel = new TestTableModel();
        final UpdateableTableModel lazyTableModel = 
               LazyTableModel.createLazyTableModel(testModel, false);
        content.add(new JScrollPane(new JTable(lazyTableModel)), 
                          BorderLayout.CENTER);
        content.add(new JButton(new AbstractAction("update") {
            public void actionPerformed(ActionEvent e) {
                lazyTableModel.update();
            }
        }), BorderLayout.SOUTH);
        frame.pack();
        frame.show();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

Conclusion

Although I'm not sure if the synchronization in triggerUpdate is really bullet proof this implementation proved itself in production.

api
guidelines
jobs

Bear
api