Как использовать JarOutputStream для создания файла JAR?
Как создать файл JAR программно, используя java.util.jar.JarOutputStream
? Файл JAR, созданный моей программой, выглядит правильно (он извлекается нормально), но когда я пытаюсь загрузить из него библиотеку, Java жалуется, что не может найти файлы, которые явно хранятся внутри нее. Если я извлечу файл JAR и использую Sun jar
инструмент командной строки для повторного сжатия, результирующая библиотека работает нормально. Короче, что-то не так с моим файлом JAR.
Пожалуйста, объясните, как создать JAR-файл программно, в комплекте с файлом манифеста.
4 ответа
Оказывается, что JarOutputStream
имеет три недокументированных причуды:
- Имена каталогов должны заканчиваться косой чертой.
- Пути должны использовать '/' косые черты, а не '\'
- Записи не могут начинаться с косой черты.
Вот правильный способ создания файла Jar:
public void run() throws IOException
{
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
JarOutputStream target = new JarOutputStream(new FileOutputStream("output.jar"), manifest);
add(new File("inputDirectory"), target);
target.close();
}
private void add(File source, JarOutputStream target) throws IOException
{
BufferedInputStream in = null;
try
{
if (source.isDirectory())
{
String name = source.getPath().replace("\\", "/");
if (!name.isEmpty())
{
if (!name.endsWith("/"))
name += "/";
JarEntry entry = new JarEntry(name);
entry.setTime(source.lastModified());
target.putNextEntry(entry);
target.closeEntry();
}
for (File nestedFile: source.listFiles())
add(nestedFile, target);
return;
}
JarEntry entry = new JarEntry(source.getPath().replace("\\", "/"));
entry.setTime(source.lastModified());
target.putNextEntry(entry);
in = new BufferedInputStream(new FileInputStream(source));
byte[] buffer = new byte[1024];
while (true)
{
int count = in.read(buffer);
if (count == -1)
break;
target.write(buffer, 0, count);
}
target.closeEntry();
}
finally
{
if (in != null)
in.close();
}
}
Есть еще одна "причуда", на которую следует обратить внимание: все имена JarEntry НЕ должны начинаться с "/".
Например: Имя записи jar для файла манифеста - "META-INF/MANIFEST.MF", а не "/META-INF/MANIFEST.MF".
То же правило должно соблюдаться для всех записей jar.
Итак, по запросу, вот код Гили, измененный для использования относительных путей, а не абсолютных. (Замените inputDirectory на выбранный вами каталог.) Я только что протестировал его, но если он не работает, дайте мне знать.
public void run() throws IOException
{
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
JarOutputStream target = new JarOutputStream(new FileOutputStream("output.jar"), manifest);
File inputDirectory = new File("inputDirectory");
for (File nestedFile : inputDirectory.listFiles())
add("", nestedFile, target);
target.close();
}
private void add(String parents, File source, JarOutputStream target) throws IOException
{
BufferedInputStream in = null;
try
{
String name = (parents + source.getName()).replace("\\", "/");
if (source.isDirectory())
{
if (!name.isEmpty())
{
if (!name.endsWith("/"))
name += "/";
JarEntry entry = new JarEntry(name);
entry.setTime(source.lastModified());
target.putNextEntry(entry);
target.closeEntry();
}
for (File nestedFile : source.listFiles())
add(name, nestedFile, target);
return;
}
JarEntry entry = new JarEntry(name);
entry.setTime(source.lastModified());
target.putNextEntry(entry);
in = new BufferedInputStream(new FileInputStream(source));
byte[] buffer = new byte[1024];
while (true)
{
int count = in.read(buffer);
if (count == -1)
break;
target.write(buffer, 0, count);
}
target.closeEntry();
}
finally
{
if (in != null)
in.close();
}
}
Вот пример кода для создания файла JAR с использованием JarOutputStream:
Вы можете сделать это с помощью этого кода:
public void write(File[] files, String comment) throws IOException {
FileOutputStream fos = new FileOutputStream(PATH + FILE);
JarOutputStream jos = new JarOutputStream(fos, manifest);
BufferedOutputStream bos = new BufferedOutputStream(jos);
jos.setComment(comment);
for (File f : files) {
print("Writing file: " + f.toString());
BufferedReader br = new BufferedReader(new FileReader(f));
jos.putNextEntry(new JarEntry(f.getName()));
int c;
while ((c = br.read()) != -1) {
bos.write(c);
}
br.close();
bos.flush();
}
bos.close();
// JarOutputStream jor = new JarOutputStream(new FileOutputStream(PATH + FILE), manifest);
}
PATH
переменная: путь к файлу JAR
FILE
переменная: имя и формат
Этот ответ решит проблему относительного пути.
private static void createJar(File source, JarOutputStream target) {
createJar(source, source, target);
}
private static void createJar(File source, File baseDir, JarOutputStream target) {
BufferedInputStream in = null;
try {
if (!source.exists()){
throw new IOException("Source directory is empty");
}
if (source.isDirectory()) {
// For Jar entries, all path separates should be '/'(OS independent)
String name = source.getPath().replace("\\", "/");
if (!name.isEmpty()) {
if (!name.endsWith("/")) {
name += "/";
}
JarEntry entry = new JarEntry(name);
entry.setTime(source.lastModified());
target.putNextEntry(entry);
target.closeEntry();
}
for (File nestedFile : source.listFiles()) {
createJar(nestedFile, baseDir, target);
}
return;
}
String entryName = baseDir.toPath().relativize(source.toPath()).toFile().getPath().replace("\\", "/");
JarEntry entry = new JarEntry(entryName);
entry.setTime(source.lastModified());
target.putNextEntry(entry);
in = new BufferedInputStream(new FileInputStream(source));
byte[] buffer = new byte[1024];
while (true) {
int count = in.read(buffer);
if (count == -1)
break;
target.write(buffer, 0, count);
}
target.closeEntry();
} catch (Exception ignored) {
} finally {
if (in != null) {
try {
in.close();
} catch (Exception ignored) {
throw new RuntimeException(ignored);
}
}
}
}