ClassPacker(with GUI shell) r1.4 / r1.0 (GUI shell)

Copyright (C) 1999,2000 Cristiano Sadun - crsadun@tin.it
Portions of this program are free software; you can redistribute them and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

See here GUI Shell related information only.

See release 1.4 enhancements.

1. Purpose

This program allows to tailor automatically a JAR file containing exactly all and only the classes needed to resolve, link and instantiate a specific Java™ class. The created JAR file contains a manifest file, optionally specifying a main class.

Note: as of 2001, ClassPacker is not under active development anymore. In my set of tools, I've substituted it with <pack>, an ant task which accomplishes similar results, besides providing added functionality as packing of additional resource/files and arbitrary classes. Source code is available for <pack> on sourceforge. CPacker is, however, fully functional as specified in this manual.

I advice you to read this first, but if you want you can download both executables and source just scrolling down. Both packages contain this documentation that can be therefore be read offline.

2. Requirements

ClassPacker requires the JDK 1.2 (aka Java 2) runtime (JRE), which can be obtained at Javasoft (http://java.sun.com). Being entirely written in Java™, it is supposed to run on every platform where the JRE 1.2 is available. Anyway, at the moment has been tested only under Win32.
It should run properly (ie: pack the classes) against 1.1 classes (and even 1.0), but - again - it has been tested only with 1.2 classes.

3. Installation

See downloading instructions. For source code installation, see Usage/Programming level.

4. Usage

4.1 User level

Console usage is straightforward; you can run the application (specifing the provided file cpacker.jar in your application class path) by invoking the class com.deltax.cpacker.ClassPacker, with the following command line arguments:

<fully qualified class name> [options]

  -o <JAR file name> uses given name for output
  -ask  prompts the user for a JAR file name
   (If not specified the simple class name +\".jar\" is used for the JAR file)
  -fnr  generates report on Class.forName() usage
  -so   the previous report is generated to stdout
   (default is forNameList.txt file)
  -ijx  includes javax.* classes
  -ij   includes necessary java.* and javax.* classes
  -append adds the entries to an existing JAR file
  -zip  use zip extension instead of JAR
  -nmf  does not create a manifest file
  -mc [class] specify mainfest main (default is class under packing)
  -skel <property file|default> (*) if a class implements java.rmi.Remote,
                                    an attempt is done to include the RMI
                                    Skeleton of an implementation class
  -stub <property file|default> (*) if a class implements java.rmi.Remote,
                                    an attempt is done to include the RMI
                                    Stub of an implementation class
  -rmi  <property file|default> (*) as specifying -stub -skel

  (*) the implmentations are searched for following the naming conventions
      described in the manual.

For example,

  java -jar cpacker.jar my.class

If you use the included cpacker.bat (Win32) or cpacker.sh (Bourne-shell) script, you will just run

   cpacker <fully qualified class name> [options]

Note: If you don't know what the class path is, or how to invoke a java class from console with command line arguments, please refer to JRE documentation (however, it's unlikely that this program is of some utility for you at this stage :^)

The <fully qualified class name> is required, since it's the name of the class you want to pack togheter with all the classes it uses.
As the smart reader should have already understood, it has to be fully qualified (i.e., you must specify com.my.wonderful.Application instead of Application).

You can optionally specify the name of the JAR file you want, by using the -o option; or, if you desire that the application asks it to you, popping up a graceful input dialog for the purpose, you can use the -ask option instead.

By default, if you don't specify any of the two, the produced JAR file name will be the not qualified name of the class, with the suffix .jar.
For example, packing com.my.wonderful.Application will result in a JAR file named Application.jar.

Okay, here's an example: supposing that

here's the command line that will produced a nicely packed Application.jar:

/jre/bin/javaw -classpath /other_classes/cpacker.jar:/classes com.my.wonderful.Application

ClassPacker will display the scan sequence and then create the JAR file, showing the progress of the operation. If the file you specify already exists, you will be prompted for confirmation before overwriting the existing one.

You can also use the option -fnr (okay, it stands for "forName report") to obtain a report of classes which make use of "Class.forName" method. These classes can potentially load (by name) classes which are beyond the reachability of ClassPacker (since such class names can be provided by String computation or even at runtime, by user input), and need closer inspection to asses which other .class files may be needed.

By default, the report is written in a file named "forNameList.txt", but using the -so option you can have it printed on screen.

By default, java.* and javax.* classes are ignored. The options -ij and -ijx (which stand for "include java.*" and "include java extensions"), allow you to override this behaviour.

A Manfest file is automatically generated, containing a list of the packed classes and their MD5 digests. If you don't want the manfifest, you can use -nmf to produce a JAR containing only the .class files.

Optionally, a main class can be specified in the JAR, making it "clickable" for Java 1.2 enabled environments. You can use -mc to instruct ClassPacker to use the class which is being packed as a main class, while specifying -mc <fully-qualified class name> sets the main class to the given class.

The default extension of the generated file is JAR. Using -zip will generate a .zip file instead.

Examples

Assumptions: (JRE1.2.x must be installed, cpacker.jar resides in /usr/local/lib/ and the class to be packed resides in /lib/classes. If you have properly set your environment, you won't need to specify the boring -cp option every time).

RMI support (Read only if your classes make use of RMI)

Version 1.3 introduces support for RMI Remote interfaces. RMI clients make use of stub classes (and skeletons, in Java 1.1 RMI) which are downloaded from the host where the implementation object resides, so normally it is not required to add them to a JAR file suited for client operation.

However, if ClassPacker is used to build JAR files for deployment, or just for class exchange, it may become necessary to deploy such files to the installation host when a class makes use of such remote object - the skeleton and stub must be added to the JAR file.

The command line options -skel, -stub and -rmi instruct ClassPacker to look for an implementation class' skeleton, stub or both whenever it encounters an interface which extends java.rmi.Remote.

Since there is no direct class-level information about which class implements a given Remote interface (at runtime, it's the ORB and the RMI system in the JVM which determine the actual skeleton/stub to use), ClassPacker has no direct way to identify such implementation. Therefore, two mechanism for specifying this information (implicitly or explicitly) are provided:

  1. Using Naming conventions

    If "default" is specified to the rmi options (like in -stub default), then, given an interface I, the following classes are searched and "tested" as possible implementations of I (for example my.package.RemoteTest):

    • <I>Impl in the same package as I (as in my.package.RemoteTestImpl)
    • <I>Server in the same package as I (as in my.package.RemoteTestServer)
    • <I> in the child package .server of the package of I (as in my.package.server.RemoteTest)
    • <I> in the child package .impl of the package of I (as in my.package.impl.RemoteTest)
    • <I>Server in the child package .server of the package of I (as in my.package.server.RemoteTestServer)
    • <I>Impl in the child package .impl of the package of I (as in my.package.impl.RemoteTestImpl)
    • <I>Server in the child package .impl of the package of I (as in my.package.impl.RemoteTestServer)
    • <I>Impl in the child package .impl of the package of I (as in my.package.impl.RemoteTestImpl)
    • <I>Server in the package <parent of I package>.server (if exists) (as in my.server.RemoteTestServer)
    • <I>Impl in the package <parent of I package>.server (if exists) (as in my.server.RemoteTestImpl)
    • <I>Server in the package <parent of I package>.impl (if exists) (as in my.impl.RemoteTestServer)
    • <I>Impl in the package <parent of I package>.impl (if exists) (as in my.impl.RemoteTestImpl)

    For example, assuming that com.deltax.test.RemoteApp makes use of the Remote interface com.deltax.test.RemoteTest, which is implemented by com.deltax.test.RemoteApp.server.RemoteTest

    java com.deltax.cpacker.ClassPacker -stub default com.deltax.test.RemoteApp

    will find the remote implementation and add the stub to JAR file.

  2. Using a property file

    A properties file may be specified instead of "default": the associations between Remote interfaces and implementation classes will be read from there. Here's an example (the file impl.properties):

    # RMI Implementation definitions for MyApplication
    #
    com.deltax.util.session.SessionManager : com.deltax.servers.SessionManagerImpl
    com.deltax.myapp.HelloObject : com.deltax.myapp.HelloObjectImpl
    

    Since com.deltax.servers does not follow the conventions above, the default search would not find it - but defining it in the properties file and running

    java com.deltax.cpacker.ClassPacker -stub impl.properties com.deltax.test.RemoteApp

    will instruct ClassPacker to look for implementation classes as defined in such files, and add the proper stub to the JAR file.

4.2 Programming level

Please skip this section if you're not interested in the source code/programming interface.

A note

Please note that not all the code used by ClassPacker is under GPL. Portions are provided under a more restrictive license - which entitles you free use of the code for not commercial purposes, but does not entitle you to have the source code without explicit request ("use" meaning both usage and redistribution for non-commercial purposes).

Anyway, this means that if you erase some of the classes contained in cpacker.jar, notably in the com.deltax.util package, you will not be able to rebuild the application. Keep always cpacker.jar with you, so in the case you just unjar the parts again.

Aligning classes and source

As first step, unjar cpacker.jar in a suitable directory, something like /classes or c:\classes. This will create the directories com/deltax/cpacker and com/deltax/util, filled with .class files. Then, unzip Cpacker10-src.zip in the same relative path: this will create the source (.java) files under GPL.

The application itself is in com/deltax/cpacker/ClassPacker.java

Javadoc documentation

In the source package you will find the Javadoc documentation of the classes involved. This is the starting point for looking at the source, besides of course direct source lookup.

Implementation notes

ClassPacker uses mainly reflection to obtain is information (but for some information which have to be dragged out the class's constant pool). A ClassPacker object owns a set of "known" classes - which is initially empty. The core method is a recursive private makeSet(Class c) method which just scans thru all the reflection information, calls various overloads of itself which in turn call itself again. When a new class type is discovered, it is added to the class set, then the method is called again upon the new class, to account for all the class types used/needed by it. That may sound complex, but it is not really - a glance at the code will clarify it all. An important issue is how the .class files are located and interpreted.

1. Locating class bytecode files

Let's start with locating the files. The standard ClassLoader does not provide information about the source of the class files themselves - it just provides information about the existence or not of a certain class (in the form of the plain or exception result of a call to forName() or findClass()). Even subclassing a ClassLoader, there is no direct access to the mechanism used by the loader itself to locate the bytecode file - for the very good reason that it's not even sure that a class is loaded from a filesystem (rather than, for example, an HTTP server).

Designing the physical loading as private implementation, however, means that - outside the JVM - it is not accessible. Still, we need to locate and inspect directly the class bytecode, whenever Java reflection is not powerful enough (see below). Therefore, a separate service for locating the ".class" files has to be provided, which emulates the behaviour (implementation) of the ClassLoader when it's trying to find a class. Luckily, information about such behaviour are publicly available and advertised - JavaSoft design simply does not allow the reuse of the code.

We decided not to directly subclass ClassLoader exposing a public method for locating .class files - but rather define a different role whose unique function is just that. This is coded as the ClassFileFinder interface.

Its implementation JDK12ClassFileFinder emulates the 1.2 locating scheme - looking at the boot class path, the extension class path, etc. Note that the behaviour emulated, however, is the "loading .class files from a filesystem" - no http loading and other devices are supported . This is sufficient in most applications - notably, it is sufficient in this application :^) - and if needed, a new implementation for the same interface can be easily built.

The main consequence of this fact is that two different pieces of code - which are supposed to have identical semantics - are used to locate and load .class file: the System ClassLoader and the ClassFileFinder. This leads to a performance problem (as the whole process of searching for the .class file has to be repeated twice) and, if a not-standard ClassLoader is used (or the emulation is bugged), to different results.

The performance issues does not appear to be a big issue with this application; anyway, it does exist.

2. Interpreting class files

Java reflection does not provide services for reverting a Class object to the bytecode stream from which the JVM builds the Class itself. This is due for a bunch of good reasons - mainly the fact that a Class exists to expose details at java programming level, where bytecode is usually not directly used/accessed.

However, Class does not expose all the information contained in the bytecode description. In particular, link information are not provided by reflection: this means that if a class is needed to execute a particular method implementation (and thus has to be linked to execute the method), but it's not declared neither as a method parameter type or return type, there's no way to discover it existence by using java reflection.

This makes perfect sense since the context for which reflection has been built and implemented (mainly beans inspection) but leaves us with the problem of discovering link information. This is accomplished in makeSet() by using a class inspector which interpretes directly the class file format and provides the needed information. The class is CPoolReader (a bad name due to the original design as a mere class' constant-pool reader) which provides the requested additional linkage information. Anyway, reflection is still used before reading the pool, since it provides a cleaner way to search type informaton for a given class.

3. Additional customization

The design allows easy customization of the the following:

4. Command line interface

Alas, the implementation of the cmdline interface and of the defaults is far from beautiful, and if adding new options is not difficult, a rewriting of this part may be done if a serious cmdline parsing is required.

5. Downloading instructions

5.1 Command line only version

You can download and install both the binary form of ClassPacker:

cpacker14-rt.zip63K

and source/documentation form; this latter includes also the previous:

cpacker14-src.zip118K

5.2 GUI Shell version

The version 2.0 (which contains the command-line v1.4 and the GUI shell 1.0) is what you most likely want:

cpacker20-rt.zip97K

cpacker20-src.zip169K

Installation is straightforward - just unzip the package and put the cpacker.jar file wherever your JRE can find it. For the source code, see Usage/Programming level.

You will probably like to tailor a small batch or shell command file to avoid typewriting every time you invoke the program. For example, as a DOS batch:

cpacker.bat:
 
@echo off
c:\jdk1.2.3\jre\bin\java -cp c:\cpacker\cpacker.jar %1 %2 %3 %4 %5

or, for Unix systems:

/usr/local/bin/cpacker:
 
#/bin/sh
/usr/local/bin/java -cp /usr/local/bin/cpacker/cpacker.jar $*


Release 2.0 enhancements/modifications

Release 1.4a enhancements/modifications

Release 1.3a enhancements/modifications

Release 1.2a enhancements/modifications

Release 1.1a enhancements/modifications


Copyright © 1999,2000 Cristiano Sadun. Portions of this software/documentation are under GPL, portions under a non-commercial free usage license.