 |
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).
|