5

I have what seems like a tricky Java library task.

I need to write an adapter/helper class for working with JTables that has some additional functionality if the JTable is a JXTable. But I don't want to add a runtime dependency on swingx-core-1.6.2.jar unless my application actually uses JXTable (in which case it already requires having the SwingX jar file on the classpath)

How can I decouple my code to accomplish this? I don't even know how I can test for JXTable; if I try to use instanceof JXTable as a test, that means my code already has an unconditional runtime dependency on JXTable.

I have written Java libraries before that have "optional" runtime linkage dependencies: if I have this in my library:

package com.foobar.foolib;

// import from whizbang.jar
import com.whizbang.BloatwareThingy;

public class SuperObject
{
    /* ... uses a BloatwareThingy ... */
}

and SuperObject is the only class that uses whizbang.jar, then as long as my end application doesn't use SuperObject, then there's no runtime dependency on whizbang.jar; if my end application does want to use SuperObject, then it needs to include whizbang.jar on the classpath. Optional from the standpoint of the application. Works great.

How can I write a method to test for a given JTable being an instance of JXTable, without requiring a dependency on the SwingX jar file if the application only uses JTable?

Jason S
  • 184,598
  • 164
  • 608
  • 970

4 Answers4

5

You can test with:

Class cls = null;
try {
    cls = Class.forName( "org.jdesktop.swingx.JXTable" );
} catch( Throwable ex ) {}

if( cls != null )
    // have JXTable

After that you would have to use Reflection exclusively for all accesses to classes, methods, constructors and fields from that external library.

As this can become very clumsy if you need to access a large API this way you could write helper classes that can use JXTable directly but get created via Reflection and invoked via interfaces or abstract classes:

public interface MyTableHandler
{
    void doSomethingWithTable( JTable table );
}

public class JXTableHandler implements MyTableHandler
{
    void doSomethingWithTable( JTable table )
    {
        JXTable jxt = (JXTable) table;
        // use JXTable API directly ...
    }
}

public class StdTableHandler implements MyTableHandler
{
    void doSomethingWithTable( JTable table )
    {
        // do without JXTable
    }
}

public class MyIndependentClass
{
    static final MyTableHandler handler;

    static {
        try {
            Class.forName( "org.jdesktop.swingx.JXTable" ); // force exception
            handler = (MyTableHandler) Class.forName( "my.pkg.JXTableHelper" )
                                            .newInstance();
        } catch( Throwable ex ) {
            handler = new StdTableHandler();
        }
    }
    public void treatTable( JTable table )
    {
        handler.doSomethingWithTable( table );
    }
}

The Java VM has no problems with the usage of nonexisting API in classes that are not used themselves but are only present in jar files. With this approach you would use JXTableHandler only if org.jdesktop.swingx.JXTable is available and StdTableHandler otherwise.

x4u
  • 13,877
  • 6
  • 48
  • 58
2

This requires reflection. Use Class.forName(...) to fetch the Class object for the JXTable class. If it is not present, it will throw ClassNotFoundException.

If it is present, you can use that Class object to fetch the methods you need and then apply them to the JTable object.

James Clark
  • 1,765
  • 13
  • 17
0

First of all, it is a compile time dependency you are trying to eliminate, not runtime.

You can check the type of the class via

if (table.getClass().getName().equals("path.to.JXTable")
{
// Do something using reflection.
}
else // proceed as normal
Robin
  • 24,062
  • 5
  • 49
  • 58
  • It's not a compile time dependency I'm trying to eliminate; I intend for my library to have a compile-time dependency on SwingX. I just don't want a *runtime* dependency on SwingX unless my application does as well. – Jason S Dec 02 '11 at 16:19
0

Aha, I got something to work:

Helper class in my library:

static private class JXTableHandler
{
    public JXTableHandler() {}

    boolean testJXTable(JTable table)
    {
        try
        {
            if (table instanceof JXTable)
                return true;
        }
        catch (java.lang.NoClassDefFoundError e) { /*gulp*/ }
        return false;
    }
    boolean handleTable(JTable table)
    {
        if (!testJXTable(table))
            return false;

        JXTable xtable = (JXTable) table;
        // here's where we do stuff, this is just something basic
        System.out.println("columnControlVisible: "
             +xtable.isColumnControlVisible());
        return true;
    }
}

Usage elsewhere in my library:

    JComponent c = ...

    if (c instanceof JTable)
    {
        JTable table = (JTable) c;
        boolean isJXTable = new JXTableHandler().handleTable(table);
        System.out.println("jtable "+
            (isJXTable ? "is" : "is not")+" a jxtable");
    }

I ran an application with it once using only JTables, without swingx on my classpath, and it works, because it catches the NoClassDefFoundError, and in that case it never attempts to access the JXTable class. If I use JXTable in my application with swingx on my classpath, it also works. (and it also "works" if I use JXTable in my application without swingx on my classpath, because the application can't instantiate a JXTable which is expected behavior that my library doesn't change.)

Jason S
  • 184,598
  • 164
  • 608
  • 970
  • JXTableHandler in this appraoch is not guaranteed to load without NoClassDefFoundErrors in `new JXTableHandler().handleTable(table); `. It should work as long as it doesn't have variables or inner classes that depend on non existing external classes. But I prefer to avoid loading such a helper class at all if the required API is not available to be sure I don't get Exceptions suddenly when the class evolves. – x4u Dec 02 '11 at 17:08