Saturday, 29 May 2010

Type-safe arbitary properties in Java

Yesterday a colleague approached me regarding the best method for associating arbitrary properties with an object in Java. The object currently has a Map<String,String> for which it exposes public setProperty and getProperty methods, but he wanted to be able to associate properties other than Strings, without having to serialize them.

I have seen Objects using a Map<String,Object> to store properties, but this leads to code becoming littered with casts, with the associated risks of ClassCastExceptions appearing at runtime.

My suggested solution was to use generics to hide the property map behind a type-safe facade.

First we define a generically typed Key:
public class Key<T> {

private final String name;

public Key(String name) {
this.name = name;
}

@Override
public int hashCode() {
return 37 * (name != null ? name.hashCode() : 0);
}

@Override
public boolean equals(Object obj) {
return this == obj;
}

@Override
public String toString() {
return "Key{" + name + "}";
}

}

When we use this as the key for our property map, the Java compiler can infer the Object type from the key, preventing us from associating a mistyped Object with a key, and correctly casting the Object back when we get it:
public class MyObject {

private Map<Key,Object> properties = new HashMap<Key, Object>();

public <T> void setProperty(Key key, T value) {
properties.put(key, value);
}

public <T> T getProperty(Key<T> key) {
return (T) properties.get(key);
}

}

The types can be determined at compile time, so there is no need for casts:
  MyObject obj = new MyObject();

Key kstr = new Key("foo");
Key kint = new Key("bar");

obj.setProperty(kstr, "string");
obj.setProperty(kint, 123);
String s = obj.getProperty(kstr);
Integer i = obj.getProperty(kint);

Errors will also be detected by the compiler:
  // Try to set a String value to an Integer-typed property
obj.setProperty(kint, "xyz");

// Try to cast the value of an Integer-typed property to a String
String x = (String) obj.getProperty(kint);

Tuesday, 27 April 2010

Creating PNGs from Jmol


Jmol is a fantastic open source 3D viewer for chemical structures. I've exported images from the application many times, both as PNGs and for rendering with POV-Ray, but after a bit of digging and trial & error I've figured out how to automate the process...

// Create the objects
java.awt.Canvas display = new Canvas();
org.jmol.adapter.smarter.SmarterJmolAdapter adapter = new SmarterJmolAdapter();
org.jmol.viewer.Viewer viewer = (Viewer)
Viewer.allocateViewer(display, adapter, null, null, null, null, null);
try {
viewer.setScreenDimension(new Dimension(1, 1));
viewer.scriptWait("load 'crystal.cif' {1 1 1};");
// can do more scripting here...
viewer.setScreenDimension(new Dimension(400, 400));

// anti-aliasing enabled
viewer.getGraphics3D().setWindowParameters(400, 400, true);

// Create image
viewer.getImageAs("PNG", 1, 400, 400, "image.png", null);
} finally {
// Ensure threads are stopped
viewer.setModeMouse(JmolConstants.MOUSE_NONE);
}

Using Jena3D's script for automatic orientation of structures, recently mentioned by Rolf Huehne on the Jmol-users mailing list I've been able to create some great pictures of crystal structures:

If only there was a really easy way for these to be used as place-holders for a lazy-loading Jmol applets, making websites embedding Jmol really fast... any volunteers?!

Tuesday, 10 February 2009

Running and distributing Maven projects

Maven makes building java projects very easy, dealing with dependencies and transitive dependencies (dependencies of dependencies, dependencies of dependencies of dependencies etc...), but what happens if you want to run a program? Or to create a distribution for people who don't use Maven?

Well, it turns out that there are several easy things we can do...

1) Run a class' main method:

The first thing we can do is run the main method of a class inside a Maven built project, having Maven take care of all the dependencies, using the exec plugin:
mvn exec:java -Dexec.mainClass=(package.className) [ -Dexec.args=(commandLineArgs) ]
We can specify which class to run, and pass arguments to its main method. To pass more than one argument the arguments need enclosing in double quotes, and separating by spaces. Spaces that are part of arguments are escaped with a slash, as are slashes...

Commandargs passed to main method
-Dexec.args=fooOne argument: 'foo'
-Dexec.args="foo"One argument: 'foo'
-Dexec.args="foo bar"Two arguments: 'foo' and 'bar'
-Dexec.args="foo\ bar"One argument: 'foo bar'
-Dexec.args="foo\\ bar"Two arguments: 'foo\' and 'bar'

2) Build a classpath:

What if we want to use our code without Maven? If we're going to be working on the machine we've been building our project on, we can get Maven to tell us how to set our classpath to point at all the dependency JAR files in our local repository:
mvn dependency:build-classpath

[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'dependency'.
[INFO] ------------------------------------------------------------------------
[INFO] Building CMLXOM
[INFO] task-segment: [dependency:build-classpath]
[INFO] ------------------------------------------------------------------------
[INFO] [dependency:build-classpath]
[INFO] Dependencies classpath:
/home/sam/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/sam/.m2/reposi
tory/jaxen/jaxen/1.1-beta-8/jaxen-1.1-beta-8.jar:/home/sam/.m2/repository/jdom/
jdom/1.0/jdom-1.0.jar:/home/sam/.m2/repository/junit/junit/4.0/junit-4.0.jar:/h
ome/sam/.m2/repository/log4j/log4j/1.2.13/log4j-1.2.13.jar:/home/sea36/.m2/repo
sitory/xalan/xalan/2.7.0/xalan-2.7.0.jar:/home/sam/.m2/repository/xerces/xerces
Impl/2.8.0/xercesImpl-2.8.0.jar:/home/sam/.m2/repository/xerces/xmlParserAPIs/2
.6.2/xmlParserAPIs-2.6.2.jar:/home/sam/.m2/repository/xml-apis/xml-apis/1.3.03/
xml-apis-1.3.03.jar:/home/sam/.m2/repository/xom/xom/1.1/xom-1.1.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
3) Export a project's dependencies out of the local repository:

Alternatively, we can export all the depencency JAR files for the project, collating them in the target/dependency directory:
mvn dependency:copy-dependencies

[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'dependency'.
[INFO] ------------------------------------------------------------------------
[INFO] Building CMLXOM
[INFO] task-segment: [dependency:copy-dependencies]
[INFO] ------------------------------------------------------------------------
[INFO] [dependency:copy-dependencies]
[INFO] Copying dom4j-1.6.1.jar to .\target\dependency\dom4j-1.6.1.jar
[INFO] Copying jaxen-1.1-beta-8.jar to .\target\dependency\jaxen-1.1-beta-8.jar
[INFO] Copying jdom-1.0.jar to .\target\dependency\jdom-1.0.jar
[INFO] Copying junit-4.0.jar to .\dependency\junit-4.0.jar
[INFO] Copying log4j-1.2.13.jar to .\target\dependency\log4j-1.2.13.jar
[INFO] Copying xalan-2.7.0.jar to .\target\dependency\xalan-2.7.0.jar
[INFO] Copying xercesImpl-2.8.0.jar to .\target\dependency\xercesImpl-2.8.0.jar
[INFO] Copying xmlParserAPIs-2.6.2.jar to .\target\dependency\xmlParserAPIs-2.6.2.jar
[INFO] Copying xml-apis-1.3.03.jar to .\target\dependency\xml-apis-1.3.03.jar
[INFO] Copying xom-1.1.jar to .\target\dependency\xom-1.1.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
4) Build a single jar containing project and all dependencies:

And finally, we can get Maven to build us a single JAR file containing both our project, and all its dependencies:
mvn assembly:assembly -DdescriptorId=jar-with-dependencies
Beware with this approach:
If there are multiple dependencies containing files with the same name and path, only one version will be included in this JAR file.

Monday, 9 February 2009

Attaching javadocs and sources to Maven install/deploy

When installing maven artifacts to your local repository, or deploying them to a remote repository, it is really helpful to attach a copy of the source code and the javadoc.

If you code Java in eclipse, you can use the m2eclipse plugin to automatically download the source and javadoc files for dependencies, and then display the associated javadoc by hovering over a method or when using dot-complete, and inspect the source code by pressing F3.

So, how do you attach the source and javadoc to an installation or deployment?
mvn clean javadoc:jar source:jar install
This will install three jar files to your local repository:
  • (artifactId)-(version).jar - containing the compiled artifact
  • (artifactId)-(version)-javadoc.jar - containing the artifact's javadoc
  • (artifactId)-(version)-sources.jar - containing the artifact's source files