It will be a heavy week, as usual, because there have been a lot of new functions added. For bnd, this actually means I will move bndlib to version 2.0.0. Except for the significantly new functionality, the API has also changed. When bndlib was small,
Map<String,Map<String,String>>
worked quite well to maintain the manifest information and package
attributes. However, in the current code base it was becoming painful.
Especially since Java has a naming fetish.
org.example.Foo.X, org/example/Foo$X, org/example/Foo$X.class,
Lorg/example/Foo
.X
and
Lorg/example/Foo$X;
are all identifying the same class in different contexts. Just imagine
how easy it is to confuse these strings. So now bndlib has Parameters,
Instructions, and Packages with lots of convenience methods. bndlib is
used in ant, maven, sbt, osmorc, bndtools, and other products. Though
the number of indirect users is quite large, the developers that program
its API is quite small. However, it is a fun library to use if you need
to work with JAR files and/or bundles. Some examples:
File asm = new File("asm.jar"); Jar jar = new Jar(asm); jar.getManifest().write(System.out);This will output the following manifest:
Manifest-Version: 1.0 Implementation-Vendor: France Telecom R&D Ant-Version: Apache Ant 1.6.2 Implementation-Title: ASM Implementation-Version: 2.2.2 Created-By: 1.5.0_04-b05 (Sun Microsystems Inc.)
As manifests go, this is actually quite good. Most people have a significantly more lonely manifests. However, since this is no bundle, we need to add OSGi headers. The following code will set the versions of the bundle version and the version of the org.objectweb.asm packages to 2.2.2. In this case we can use a macro. We could create a special macro, version, for this and use this in the Bundle-Version and Export-Package headers. However, we can also reuse the Bundle-Version header since any header is also a macro. Notice that we use a time stamp on the version to find out about the build date.
Analyzer analyzer = new Analyzer(); analyzer.setJar(jar); analyzer.setProperty("Bundle-Version", "2.2.2.${tstamp}"); analyzer.setExportPackage("org.objectweb.asm.*;version=${Bundle-Version}"); Manifest manifest = analyzer.calcManifest(); jar.setManifest(manifest); jar.getManifest().write(System.out);
This provides the following manifest:
Manifest-Version: 1.0 Export-Package: org.objectweb.asm;version="2.2.2.201206081457",org.obj ectweb.asm.signature;version="2.2.2.201206081457" Implementation-Title: ASM Implementation-Version: 2.2.2 Tool: Bnd-1.52.2 Bundle-Name: showcase Created-By: 1.6.0_27 (Apple Inc.) Implementation-Vendor: France Telecom R&D Ant-Version: Apache Ant 1.6.2 Bundle-Version: 2.2.2.201206081457 Bnd-LastModified: 1339160222619 Bundle-ManifestVersion: 2 Bundle-SymbolicName: showcase Originally-Created-By: 1.5.0_04-b05 (Sun Microsystems Inc.)bnd added defaults for crucial OSGi information that was missing. The name, symbolic name, version, etc. It also copied all the headers from the old jar so that no information is lost. However, most important it calculated the Export-Package header.
Bundle-Version: 2.2.2.201206081456 Export-Package: org.objectweb.asm;version="2.2.2.201206081457", org.objectweb.asm.signature;version="2.2.2.201206081457"So lets save the jar on disk, including the digests so the bundle can be verified:
jar.calcChecksums(new String[] {"SHA", "MD5"}); jar.write("asm-2.2.2.jar");So what more can we do? Lets take some of our own code and create a JAR out of it. The following example takes code from the bin directory, packages it and links it to the asm on disk.
Builder b = new Builder(); b.setPrivatePackage("simple"); b.addClasspath(asm); b.addClasspath(new File("bin")); Jar simple = b.build(); simple.getManifest().write(System.out);The Private-Package will copy any package it specifies from the class path to the Jar. Since the asm on disk has no OSGi headers, we do not get import ranges.
Import-Package: org.objectweb.asm
So lets use the Jar we've just created instead, and lets also export the simple package.
Builder b = new Builder(); b.setExportPackage("simple"); b.addClasspath(jar); b.addClasspath(new File("bin")); Jar simple = b.build();
Since the asm built jar we added has versions, the imports have version ranges. bnd also calculates the uses constraints on the exported packages:
Export-Package: simple;uses:="org.objectweb.asm";version="1.0.0" Import-Package: org.objectweb.asm;version="[2.2,3)"
Last, lets say you want to know all the references from a JAR:
Analyzer analyzer = new Analyzer(); analyzer.setJar(j3); analyzer.analyze(); System.out.println("Referred " + analyzer.getReferred()); System.out.println("Contains " + analyzer.getContained()); System.out.println("Uses" ); for ( Entry<PackageRef, List<PackageRef>> from : analyzer.getUses().entrySet()) System.out.printf(" %-40s %s\n", from.getKey(), new TreeSet<PackageRef>(from.getValue()) );
Which gives the following output:
Referred java.lang,org.objectweb.asm Contains simple Imports [org.objectweb.asm] Exports [simple] Uses simple [java.lang, org.objectweb.asm]
This blog could go on forever (ok, for quite a long time); there is quite a lot of useful functionality in the API. However, for normal usage bnd(tools) works best since it has a nicer user interface, integrates with continuous integration, and is tremendously nice to develop with. However, if you find yourself processing jars or OSGi bundles, consider working with the API.
Peter Kriens
What about the Eclipse plugin? Is it going to be released together?
ReplyDelete