The Source for Java Technology Collaboration
User: Password:



Laird Nelson's Blog

September 2004 Archives


Cheap Hack I: rename your jar file, get a different Main-Class

Posted by ljnelson on September 20, 2004 at 12:09 PM | Permalink | Comments (4)

Here's a fun hack.

I (like everyone else in the world) have a collection of utilities I take with me from job to job. It's served me well for several years now. These utilities are simple, domain-independent things: classes to copy files, find out what jar file a class is loading from, that sort of thing.

Many of the classes in this utility collection jar have main() methods, and could conceivably serve as the Main-Class in an executable jar somewhere. But I don't want to get into the business of fragmenting my utility jar. What to do?

Feeling hackish, I put together a class that itself is installed as the Main-Class in a jar file, but which consults a file (also in the jar) to let it know what class to actually use as the main class. The hackish part is that all you have to do is rename the jar file for the "right" Main-Class to be selected. Here's the (quick, dirty, uncommented) code:

import java.io.InputStream;
import java.io.IOException;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import java.net.URL;

import java.util.Properties;

public final class Main {

  private Main() {
    super();
  }

  public static final void main(final String[] args) throws Throwable {
    final URL location;
    final String classLocation = Main.class.getName().replace('.', '/') + ".class";
    final ClassLoader loader = Main.class.getClassLoader();
    if (loader == null) {
      location = ClassLoader.getSystemResource(classLocation);
    } else {
      location = loader.getResource(classLocation);
    }
    String token = null;
    if (location != null && "jar".equals(location.getProtocol())) {
      String urlString = location.toString();
      if (urlString != null) {
        final int lastBangIndex = urlString.lastIndexOf("!");
        if (lastBangIndex >= 0) {
          urlString = urlString.substring("jar:".length(), lastBangIndex);
          if (urlString != null) {
            final int lastSlashIndex = urlString.lastIndexOf("/");
            if (lastSlashIndex >= 0) {
              token = urlString.substring(lastSlashIndex + 1);
            }
          }
        }
      }
    }
    if (token != null) {
      InputStream stream = null;
      try {
        if (loader == null) {
          stream = ClassLoader.getSystemResourceAsStream("mainClasses");
        } else {
          stream = loader.getResourceAsStream("mainClasses");
        }
        if (stream != null) {
          final Properties properties = new Properties(System.getProperties());
          properties.load(stream);
          final String mainClassName = properties.getProperty(token + ".main.class");
          if (mainClassName != null) {
            final Class mainClass = Class.forName(mainClassName);
            if (mainClass != null) {
              final Method mainMethod = mainClass.getDeclaredMethod("main", new Class[] { args.getClass() });
              if (mainMethod != null && Void.TYPE.equals(mainMethod.getReturnType())) {
                final int modifiers = mainMethod.getModifiers();
                if (Modifier.isPublic(modifiers) &&
                    Modifier.isStatic(modifiers)) {
                  try {
                    mainMethod.invoke(null, new Object[] { args });
                  } catch (final InvocationTargetException kaboom) {
                    throw kaboom.getTargetException();
                  }
                }
              }
            }
          }
        }
      } finally {
        if (stream != null) {
          try {
            stream.close();
          } catch (final IOException ignore) {
            // ignore
          }
        }
      }
    }
  }

}

There are two interesting bits to this. The first is the way that a class' location is returned as a URL. Suppose you have a class, com.foo.bar.Baz. And suppose that class is present in a jar file whose (Windows, let's say) path is C:\x\y\z.jar. Then if you say:

URL url =
classLoader.getResource("com/foo/bar/Baz.class");
...the URL you'll get back is:
jar:file:C:/x/y/z.jar!/com/foo/bar/Baz.class
This format, which is documented as part of the java.net.JarURLConnection class, is remarkably useful. The URL format, as you can see, gives you both the directory structure of the jar file itself, as well as the location of the jar file. In the code above, we use this URL format to figure out what the unqualified name of the jar file is--the "basename":
String urlString = location.toString();
if (urlString != null) {
  final int lastBangIndex = urlString.lastIndexOf("!");
  if (lastBangIndex >= 0) {
    urlString = urlString.substring("jar:".length(), lastBangIndex);
    if (urlString != null) {
      final int lastSlashIndex = urlString.lastIndexOf("/");
      if (lastSlashIndex >= 0) {
        token = urlString.substring(lastSlashIndex + 1);
      }
    }
  }
}
Here we simply lop off everything from the "jar:" part up to and including the last slash (well, the last slash before the "!" delimiter). Then we lop off everything from (and including) the "!" delimiter to give us the "basename" of the jar file, which, in our contrived example, would simply be z.jar.

Armed with that as a key, we now go looking for a well-known file inside the jar. I simply called it "mainClasses". It is a simple Properties dump, and is accessible via the usual getResourceAsStream() machinery:

stream =
loader.getResourceAsStream("mainClasses");
(If you want to be really careful, you would actually open the jar file yourself and extract this entry from it. The problem with the way we've done it here is that if there is any file in the classpath named mainClasses that appears before the jar file in question, it will be selected instead.)

If we found it, we load a new Properties object from it, where, hopefully, the keys are jar "basenames", and the values are fully qualified names of classes, each of which will serve as that jar file's Main-Class. Here's an example of the contents of that file:

z.jar: com.foo.bar.Xyzzy
q.jar: com.foo.bar.Frobnicator
This file indicates that if the jar is named z.jar, then its main class will be com.foo.bar.Xyzzy. If, on the other hand, the jar is named q.jar, then its main class will be com.foo.bar.Frobnicator.

The rest of the code is simply the process of loading the correct class via reflection and seeing if it has a public static void main(String[]) method on it. Note that the only kind of Exception we explicitly catch is InvocationTargetException so that if something goes wrong in the delegate main class, it will look (as much as possible) as though that class were directly invoked.

Obviously this dirty little hack will only scratch an itch if it makes sense to copy the jar file to multiple places with different names. In my case it does, because my utility jar file is very small.



Seventeenth century object design

Posted by ljnelson on September 02, 2004 at 08:10 AM | Permalink | Comments (16)

Hello, first of all. It's an honor to be part of the java.net weblogging community.

A discussion concerning component reuse brought me back to my philosophy major days in college. Who knew British empiricism could help you with object design?

The discussion was (is) about, loosely, how best to design an object or a component for reuse. You want to get the hypothetical object down to what's important across domains, to trim away the fat, but, as a competing concern, to make it rich enough that people won't have to reinvent the wheel. Slimming down your object also provides incentive for someone else to use it, provided you don't slim it down so far that it's impractical.

What is this "fat" we're talking about? In general, it's concerns or aspects or facets of the real-world thing that our object is modeling. There are many facets about a hypothetical and reusable Person object (and the set of all persons that it models), for example, that could be included, ranging from names to measurements to relationships with others. Which of these groups of attributes is necessary for a Person to be a Person? Which is superfluous? What is the Person apart from these groups of attributes?

John Locke examined these issues in An Essay Concerning Human Understanding. Ruthlessly paraphrased, and butchered somewhat for this weblog's purposes, he argues that when you hear someone mention a person, you frame a "complication or collection" of "simple ideas" in your mind that "go constantly together", such as height, weight, general shape, etc. In Locke's view we humans in the normal unexamined course of our lives don't imagine these ideas simply existing on their own, so we subconsciously posit the existence of a "substratum" that they bind to, although strictly speaking we never entirely know what we're talking about. Locke calls this substratum substance, and, believe it or not, it's one way of thinking about object reuse, whether it exists or not.

A couple of choice quotes from Locke on this whole subject:

If any one should be asked, what is the subject wherein colour or weight inheres, he would have nothing to say, but the solid extended parts; and if he were demanded, what is it that solidity and extension adhere in, he would... [answer] SOMETHING, HE KNEW NOT WHAT.
...[O]ur...ideas of substances...have always the confused idea of something to which they belong...and therefore when we speak of any sort of substance, we say it is a thing having such or such qualities; as body is a thing that is extended, figured, and capable of motion; spirit, a thing capable of thinking; and so hardness, friability, and power to draw iron, we say, are qualities to be found in a loadstone. These...intimate that the substance is supposed always SOMETHING BESIDES the extension, figure, solidity, motion, thinking, or other observable ideas, though we know not what it is.

Let's apply Locke to object design and see where it leads. Suppose he's right: a person is simply a collection of related attributes or facets that adhere to some substratum. Let's model this explicitly.

Backing up for a second for contrast, the normal way you would design a domain-independent Person object, assuming for the moment you could, would be to pick some attributes that you thought were sufficiently general and important to be included in this superclass (and here's where you run into problems). Here, for the sake of brevity, is my silly offering:

public class Person {
  
  // My selection for an attribute that is important enough to
  // be included in this reusable class.  Yours could be different.
  // Of course that means we'll probably end up not reusing each other's
  // classes.  Hmm.
  private String name;

  public Person() {
    super();
  }

  public String getName() {
    return this.name;
  }

  public void setName(final String name) {
    this.name = name;
  }

  // etc.

}

Of course, picking what those attributes are is what makes reusable object design so tough.

Locke, if he were an object designer, would be a rebel. He would probably start with the attributes--the collections of simple ideas--and graft them on to some simple "substance" object that would be deliberately designed to be more or less inscrutable. As it turns out, this is a much better way to achieve object reuse across domains. Maybe an extreme version of his code would look something like this:

public interface IKnowNotWhat {
  // If only we knew what it was!
}

public class Person implements IKnowNotWhat {

  public Person() {
    super();
  }

}

public abstract class CollectionOfSimpleIdeas {
    
  protected final IKnowNotWhat substance;

  public CollectionOfSimpleIdeas(final IKnowNotWhat substance) {
    super();
    this.substance = substance;
    if (substance == null) {
      throw new BritishEmpiricismMisunderstoodException();
    }
  }

  public IKnowNotWhat getSubstance() {
    return substance;
  }

}

public class PersonNames extends CollectionOfSimpleIdeas {
  
  private String familiarName;

  public PersonNames(final IKnowNotWhat substance) {
    super(substance);
  }

  public String getFamiliarName() {
    return this.familiarName;
  }

  public void setFamiliarName(final String familiarName) {
    this.familiarName = familiarName;
  }

  // etc.

}

public class PersonMeasurements extends CollectionOfSimpleIdeas {
    
  private int heightInInches;

  public PersonMeasurements(final IKnowNotWhat substance) {
    super(substance);
  }

  public int getHeight() {
    return this.heightInInches;
  }

  public void setHeight(final int height) {
    this.heightInInches = height;
  }

  // etc.

}

The key insight here is that people aren't interested in a reusable Person object at all. They are interested in different combinations of facets that all concern a person in a particular domain. A financial services programmer doesn't care about a person's weight, but that might be very important to the programmer of an insurance system. Explicitly modeling a person this way--inside out--lets each domain of attributes stand alone, but also lets them play nicely together when bound together. What's important is that the attributes are reused, not the substratum.

All of this philosophy has made my brain hurt. I'm going to go get some lunch (although I know not what).





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds