Attached you will find a patch against the trunk for
com.sun.jini.tool.JarWrapper that contains the aggregated code changes
for the issues RIVER-116 and RIVER-161. This has been used for a while
and functions OK as far as I can tell.
Please review and let me know what you think.
--
Mark
Index: ../src/com/sun/jini/tool/JarWrapper.java
===================================================================
--- ../src/com/sun/jini/tool/JarWrapper.java (revision 636069)
+++ ../src/com/sun/jini/tool/JarWrapper.java Tue Feb 12 21:58:20 CET 2008
@@ -33,7 +33,6 @@
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
@@ -115,21 +114,23 @@
* java -jar <var><b>jsk_install_dir</b></var>\lib\jarwrapper.jar
<var><b>processing_options</b></var>
* </pre></blockquote>
* <p>
- * A more specific example with options for running directly from a Unix
command line might be:
+ * A more specific example with options for running directly from a Unix
command
+ * line might be:
* <blockquote><pre>
* % java -jar <var><b>install_dir</b></var>/lib/jarwrapper.jar \
* -httpmd=SHA-1 wrapper.jar base_dir src1.jar src2.jar
* </pre></blockquote>
- * where <var><b>jsk_install_dir</b></var> is the directory where the Apache
River release
- * is installed. This command line would result in the creation of a wrapper
- * JAR file, <code>wrapper.jar</code>, in the current working directory, whose
- * contents would be based on the source JAR files <code>src1.jar</code> and
- * <code>src2.jar</code> (as well as any other JAR files referenced
- * transitively through their <code>Class-Path</code> attributes or JAR
- * indexes). The paths for <code>src1.jar</code> and <code>src2.jar</code>, as
- * well as any transitively referenced JAR files, would be resolved relative to
- * the <code>base_dir</code> directory. The <code>Class-Path</code> attribute
- * of <code>wrapper.jar</code> would use HTTPMD URLs with SHA-1 digests.
+ * where <var><b>jsk_install_dir</b></var> is the directory where the Apache
+ * River release is installed. This command line would result in the creation
+ * of a wrapper JAR file, <code>wrapper.jar</code>, in the current working
+ * directory, whose contents would be based on the source JAR files
+ * <code>src1.jar</code> and <code>src2.jar</code> (as well as any other JAR
+ * files referenced transitively through their <code>Class-Path</code>
+ * attributes or JAR indexes). The paths for <code>src1.jar</code> and
+ * <code>src2.jar</code>, as well as any transitively referenced JAR files,
+ * would be resolved relative to the <code>base_dir</code> directory. The
+ * <code>Class-Path</code> attribute of <code>wrapper.jar</code> would use
+ * HTTPMD URLs with SHA-1 digests.
* <p>
* The equivalent programmatic invocation of <code>JarWrapper</code> would be:
* <blockquote><pre>
@@ -180,12 +181,16 @@
Logger.getLogger(JarWrapper.class.getName());
private final File destJar;
+ /**
+ * Base directory, <code>null</code> in case the JAR files are specified as
+ * absolute files, the so called flatten classpath option.
+ */
private final File baseDir;
private final SourceJarURL[] srcJars;
private final Manifest manifest;
private final MessageDigest digest;
private final JarIndexWriter indexWriter;
- private final PreferredListWriter prefWriter = new PreferredListWriter();
+ private final PreferredListWriter prefWriter;
private final StringBuffer classPath = new StringBuffer();
private String mainClass = null;
private final Set seenJars = new HashSet();
@@ -200,22 +205,36 @@
String[] srcJars,
String httpmdAlg,
boolean index,
- Manifest mf)
+ Manifest mf,
+ List apiClasses)
{
this.destJar = new File(destJar);
if (this.destJar.exists()) {
throw new LocalizedIllegalArgumentException(
"jarwrapper.fileexists", destJar);
}
+ if (baseDir != null) {
this.baseDir = new File(baseDir);
if (!this.baseDir.isDirectory()) {
throw new LocalizedIllegalArgumentException(
"jarwrapper.invalidbasedir", baseDir);
}
+ }
+ else {
+ this.baseDir = null;
+ }
this.srcJars = new SourceJarURL[srcJars.length];
for (int i = 0; i < srcJars.length; i++) {
try {
- SourceJarURL url = new SourceJarURL(srcJars[i]);
+ SourceJarURL url;
+ if (baseDir == null) {
+ File file = new File(srcJars[i]);
+ url = new SourceJarURL(file.getName(),
+ file.getParentFile());
+ }
+ else {
+ url = new SourceJarURL(srcJars[i]);
+ }
if (url.algorithm != null) {
throw new LocalizedIllegalArgumentException(
"jarwrapper.urlhasdigest", url);
@@ -241,7 +260,18 @@
}
manifest = mf != null ? new Manifest(mf) : new Manifest();
indexWriter = index ? new JarIndexWriter() : null;
+ List classes = new ArrayList();
+ if (apiClasses != null) {
+ for (Iterator classNames = apiClasses.iterator();
+ classNames.hasNext();) {
+ String className = (String) classNames.next();
+ if (className != null) {
+ classes.add(className.replace('.', '/') + ".class");
- }
+ }
+ }
+ }
+ prefWriter = new PreferredListWriter(classes);
+ }
/**
* Generates a wrapper JAR file for the specified JAR files. The command
@@ -458,10 +488,96 @@
Manifest mf)
throws IOException
{
- new JarWrapper(destJar, baseDir, srcJars, httpmdAlg, index, mf).wrap();
+ wrap(destJar, baseDir, srcJars, httpmdAlg, index, mf, null);
}
/**
+ * Generates a wrapper JAR file based on the provided values in the same
+ * manner as described in the documentation for [EMAIL PROTECTED] #main}.
The only
+ * difference between this method and <code>main</code> is that it receives
+ * its values as explicit arguments instead of in a command line, and
+ * indicates failure by throwing an exception.
+ *
+ * @param destJar name of the wrapper JAR file to generate
+ * @param baseDir base directory from which to locate source JAR
+ * files to wrap
+ * @param srcJars list of top-level source JAR files to process
+ * @param httpmdAlg name of algorithm to use for generating HTTPMD URLs, or
+ * <code>null</code> if plain HTTP URLs should be used
+ * @param index if <code>true</code>, generate a JAR index; if
+ * <code>false</code>, do not generate one
+ * @param mf manifest containing values to include in the manifest file
+ * of the generated wrapper JAR file
+ * @param apiClasses list of binary class names (type <code>String</code>)
+ * that must be considered API classes in case a preferences conflict
+ * arrises during wrapping of the JAR files, or <code>null</code> in case
+ * no such list is available
+ *
+ * @throws IOException if an I/O error occurs while processing source JAR
+ * files or generating the wrapper JAR file
+ * @throws IllegalArgumentException if the provided values are invalid
+ * @throws NullPointerException if <code>destJar</code>,
+ * <code>baseDir</code>, <code>srcJars</code>, or any element of
+ * <code>srcJars</code> is <code>null</code>
+ */
+ public static void wrap(String destJar,
+ String baseDir,
+ String[] srcJars,
+ String httpmdAlg,
+ boolean index,
+ Manifest mf,
+ List apiClasses)
+ throws IOException
+ {
+ new JarWrapper(destJar, baseDir, srcJars, httpmdAlg, index, mf,
+ apiClasses).wrap();
+ }
+
+ /**
+ * Generates a wrapper JAR file based on the provided values in the same
+ * manner as described in the documentation for [EMAIL PROTECTED] #main}.
+ * <p>
+ * The difference between this method and the 6-arg <code>wrap</code>
method
+ * is that the source JAR files must be specified by an absolute path and
+ * that for processing the classpath will be flattened, i.e. each source
JAR
+ * file will be considered as relative to its parent directory (that will
+ * serve as a virtual base directory) for the assembly of the
+ * <code>Class-Path</code> entry.
+ *
+ * @param destJar name of the wrapper JAR file to generate
+ * @param srcJars list of top-level source JAR files to process, must be
+ * absolute paths
+ * @param httpmdAlg name of algorithm to use for generating HTTPMD URLs, or
+ * <code>null</code> if plain HTTP URLs should be used
+ * @param index if <code>true</code>, generate a JAR index; if
+ * <code>false</code>, do not generate one
+ * @param mf manifest containing values to include in the manifest file
+ * of the generated wrapper JAR file
+ * @param apiClasses list of binary class names (type <code>String</code>)
+ * that must be considered API classes in case a preferences conflict
+ * arrises during wrapping of the JAR files, or <code>null</code> in case
+ * no such list is available
+ *
+ * @throws IOException if an I/O error occurs while processing source JAR
+ * files or generating the wrapper JAR file
+ * @throws IllegalArgumentException if the provided values are invalid
+ * @throws NullPointerException if <code>destJar</code>,
+ * <code>srcJars</code>, or any element of <code>srcJars</code> is
+ * <code>null</code>
+ */
+ public static void wrap(String destJar,
+ String[] srcJars,
+ String httpmdAlg,
+ boolean index,
+ Manifest mf,
+ List apiClasses)
+ throws IOException
+ {
+ new JarWrapper(destJar, null, srcJars, httpmdAlg, index, mf,
+ apiClasses).wrap();
+ }
+
+ /**
* Processes source JAR files and outputs wrapper JAR file.
*/
private void wrap() throws IOException {
@@ -481,7 +597,7 @@
private void process(SourceJarURL url, PreferredListReader prefReader)
throws IOException
{
- File file = url.toFile(baseDir);
+ File file = baseDir == null ? url.toFile() : url.toFile(baseDir);
boolean seen = seenJars.contains(file);
boolean checkMainClass = mainClass == null && prefReader == null;
if (seen && !checkMainClass) {
@@ -680,24 +796,27 @@
* Returns localized message text corresponding to the given key string.
*/
static String localize(String key) {
- String fmt = getResourceString(key);
- if (fmt == null) {
- fmt = "no text found: \"" + key + "\"";
+ return localize(key, new Object[0]);
- }
+ }
- // REMIND: format even without arguments?
- return MessageFormat.format(fmt, null);
- }
/**
* Returns localized message text corresponding to the given key string,
* passing the provided value as an argument when formatting the message.
*/
static String localize(String key, Object val) {
+ return localize(key, new Object[] { val });
+ }
+
+ /**
+ * Returns localized message text corresponding to the given key string,
+ * passing the provided values as an argument when formatting the message.
+ */
+ static String localize(String key, Object[] vals) {
String fmt = getResourceString(key);
if (fmt == null) {
- fmt = "no text found: \"" + key + "\" {0}";
+ return "error: no text found in resource bundle for key: " + key;
}
- return MessageFormat.format(fmt, new Object[]{ val });
+ return MessageFormat.format(fmt, vals);
}
/**
@@ -774,7 +893,16 @@
LocalizedIOException(String key, Object val) {
super(localize(key, val));
}
+
+ /**
+ * Creates exception with localized message text corresponding to the
+ * given key string, passing the provided values as an argument when
+ * formatting the message.
+ */
+ LocalizedIOException(String key, Object[] vals) {
+ super(localize(key, vals));
- }
+ }
+ }
/**
* Represents URL to a source JAR file. Source JAR URLs must be relative,
@@ -795,6 +923,11 @@
final String digest;
/** HTTPMD digest comment, or null if non-HTTPMD URL */
final String comment;
+ /**
+ * Base directory associated with relative path for JAR file, only
+ * set in case the flatten classpath option is used.
+ */
+ private File baseDir;
/**
* Creates SourceJarURL based on given raw URL string.
@@ -838,6 +971,16 @@
}
/**
+ * Creates SourceJarURL based on given raw URL string that has an
+ * individual associated base directory.
+ */
+ SourceJarURL(String raw, File baseDir) throws IOException {
+ this(raw);
+
+ this.baseDir = baseDir;
+ }
+
+ /**
* Creates SourceJarURL based on given components.
*/
SourceJarURL(String path,
@@ -875,6 +1018,13 @@
/**
* Returns file represented by this URL.
*/
+ File toFile() {
+ return toFile(baseDir);
+ }
+
+ /**
+ * Returns file represented by this URL.
+ */
File toFile(File base) {
try {
String p = new URI(path).getPath(); // decode path
@@ -1230,8 +1380,18 @@
private final HashMap pathMap = new HashMap();
private final DirNode rootNode = new DirNode("");
private int numPrefs = 0;
+ private final List apiClasses;
- PreferredListWriter() {
+
+ /**
+ * Constructs a <code>PreferredListWriter</code>.
+ *
+ * @param apiClasses list of URI paths representing classes that must be
+ * considered API classes in case a preferences conflict arrises during
+ * wrapping of JAR files
+ */
+ PreferredListWriter(List apiClasses) {
+ this.apiClasses = apiClasses;
pathMap.put("", rootNode);
}
@@ -1252,7 +1412,7 @@
pref ? "preferred: {0}" : "not preferred: {0}",
new Object[]{ path });
}
- addFile(path, pref);
+ addFile(path, jar.getName(), pref);
}
}
}
@@ -1282,19 +1442,31 @@
/**
* Records the preferred setting of the given file entry.
*/
- private void addFile(String path, boolean preferred)
+ private void addFile(String path, String jarFileName, boolean preferred)
throws IOException
{
FileNode fn = (FileNode) pathMap.get(path);
if (fn != null) {
if (fn.preferred != preferred) {
+ // in case it is part of what are considered API classes
+ // we correct the preferred value if required and correct
+ // the total number of preferred classes encountered
+ if (apiClasses.contains(path)) {
+ if (fn.preferred) {
+ fn.preferred = false;
+ numPrefs--;
+ }
+ }
+ else {
- throw new LocalizedIOException(
+ throw new LocalizedIOException(
- "jarwrapper.prefconflict", path);
+ "jarwrapper.prefconflict",
+ new Object[] { path, jarFileName, fn.jarFileName });
- }
+ }
+ }
return;
}
- fn = new FileNode(path, preferred);
+ fn = new FileNode(path, jarFileName, preferred);
pathMap.put(path, fn);
if (preferred) {
numPrefs++;
@@ -1387,12 +1559,14 @@
static final int INCLUDE = 2;
final String path;
- final boolean preferred;
+ final String jarFileName;
+ boolean preferred;
int action;
- FileNode(String path, boolean preferred) {
+ FileNode(String path, String jarFileName, boolean preferred) {
this.path = path;
this.preferred = preferred;
+ this.jarFileName = jarFileName;
}
}