Adicionar arquivo ao JAR via CODIGO

Estava pesquisando como adicionar um arquivo ao JAR via codigo java. Vi que utiliza muitos conceitos que ainda não conheço :cry: , ai encontrei a classe abaixo.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.zip.CRC32;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

public final class JarUtil {

    private final static boolean IS_WINDOWS = (File.separatorChar == '\\');

    /**
     * Writes all files of the given directory to the specified jar-file.
     * 
     * @param sourceDir
     *            The directory containing the "source" files.
     * @param target
     *            The jar file which should be created
     * @param compress
     *            True when the jar file should be compressed
     * @throws FileNotFoundException
     *             when a file could not be found
     * @throws IOException
     *             when a file could not be read or the jar file could not be
     *             written to.
     */
    public static void jar(File sourceDir, File target, boolean compress)
            throws IOException {
        jar(sourceDir, target, compress);
    }

    /**
     * Writes all given files to the specified jar-file.
     * 
     * @param files
     *            all files that should be added to the JAR file
     * @param sourceDir
     *            The parent directory containing the given files.
     * @param target
     *            The jar file which should be created
     * @param compress
     *            True when the jar file should be compressed
     * @throws FileNotFoundException
     *             when a file could not be found
     * @throws IOException
     *             when a file could not be read or the jar file could not be
     *             written to.
     */
    public static void jar(File sourceDir, OutputStream target,
            boolean compress) throws IOException {
        File[] files = sourceDir.listFiles();
        // creates target-jar-file:
        JarOutputStream out = new JarOutputStream(target);
        if (compress) {
            out.setLevel(ZipOutputStream.DEFLATED);
        } else {
            out.setLevel(ZipOutputStream.STORED);
        }
        // create a CRC32 object:
        CRC32 crc = new CRC32();
        byte[] buffer = new byte[1024 * 1024];
        // add all files:
        int sourceDirLength = sourceDir.getAbsolutePath().length() + 1;
        for (File file : files) {
            addFile(file, out, crc, sourceDirLength, buffer);
        }
        out.close();
    }

    /**
     * Adds one file to the given jar file. If the specified file is a
     * directory, all included files will be added.
     * 
     * @param file
     *            The file which should be added
     * @param out
     *            The jar file to which the given jar file should be added
     * @param crc
     *            A helper class for the CRC32 calculation
     * @param sourceDirLength
     *            The number of chars which can be skipped from the file's path
     * @param buffer
     *            A buffer for reading the files.
     * @throws FileNotFoundException
     *             when the file was not found
     * @throws IOException
     *             when the file could not be read or not be added
     */
    private static void addFile(File file, JarOutputStream out, CRC32 crc,
            int sourceDirLength, byte[] buffer) throws FileNotFoundException,
            IOException {
        if (file.isDirectory()) {
            File[] fileNames = file.listFiles();
            for (int i = 0; i < fileNames.length; i++) {
                addFile(fileNames[i], out, crc, sourceDirLength, buffer);
            }
        } else {
            String entryName = file.getAbsolutePath().substring(sourceDirLength);
            if (IS_WINDOWS) {
                entryName = entryName.replace('\\', '/');
            }
            JarEntry entry = new JarEntry(entryName);
            // read file:
            FileInputStream in = new FileInputStream(file);
            add(entry, in, out, crc, buffer);
        }
    }

    /**
     * @param entry
     * @param in
     * @param out
     * @param crc
     * @param buffer
     * @throws IOException
     */
    private static void add(JarEntry entry, InputStream in,
            JarOutputStream out, CRC32 crc, byte[] buffer) throws IOException {
        out.putNextEntry(entry);
        int read;
        long size = 0;
        while ((read = in.read(buffer)) != -1) {
            crc.update(buffer, 0, read);
            out.write(buffer, 0, read);
            size += read;
        }
        entry.setCrc(crc.getValue());
        entry.setSize(size);
        in.close();
        out.closeEntry();
        crc.reset();
    }

    /**
     * Adds the given file to the specified JAR file.
     * 
     * @param file
     *            the file that should be added
     * @param jarFile
     *            The JAR to which the file should be added
     * @param parentDir
     *            the parent directory of the file, this is used to calculate
     *            the path witin the JAR file. When null is given, the file will
     *            be added into the root of the JAR.
     * @param compress
     *            True when the jar file should be compressed
     * @throws FileNotFoundException
     *             when the jarFile does not exist
     * @throws IOException
     *             when a file could not be written or the jar-file could not
     *             read.
     */
    public static void addToJar(File file, File jarFile, File parentDir, boolean compress) throws FileNotFoundException, IOException {
        File tmpJarFile = File.createTempFile("tmp", ".jar", jarFile.getParentFile());
        JarOutputStream out = new JarOutputStream(new FileOutputStream(
                tmpJarFile));
        if (compress) {
            out.setLevel(ZipOutputStream.DEFLATED);
        } else {
            out.setLevel(ZipOutputStream.STORED);
        }
        // copy contents of old jar to new jar:
        JarFile inputFile = new JarFile(jarFile);
        JarInputStream in = new JarInputStream(new FileInputStream(jarFile));
        CRC32 crc = new CRC32();
        byte[] buffer = new byte[512 * 1024];
        JarEntry entry = (JarEntry) in.getNextEntry();
        while (entry != null) {
            InputStream entryIn = inputFile.getInputStream(entry);
            add(entry, entryIn, out, crc, buffer);
            entryIn.close();
            entry = (JarEntry) in.getNextEntry();
        }
        in.close();
        inputFile.close();

        int sourceDirLength;
        if (parentDir == null) {
            sourceDirLength = file.getAbsolutePath().lastIndexOf(
                    File.separatorChar) + 1;
        } else {
            sourceDirLength = file.getAbsolutePath().lastIndexOf(
                    File.separatorChar)
                    + 1 - parentDir.getAbsolutePath().length();
        }
        addFile(file, out, crc, sourceDirLength, buffer);
        out.close();

        // remove old jar file and rename temp file to old one:
        if (jarFile.delete()) {
            if (!tmpJarFile.renameTo(jarFile)) {
                throw new IOException(
                        "Unable to rename temporary JAR file to ["
                        + jarFile.getAbsolutePath() + "].");
            }
        } else {
            throw new IOException("Unable to delete old JAR file ["
                    + jarFile.getAbsolutePath() + "].");
        }

    }

    /**
     * Extracts the given jar-file to the specified directory. The target
     * directory will be cleaned before the jar-file will be extracted.
     * 
     * @param jarFile
     *            The jar file which should be unpacked
     * @param targetDir
     *            The directory to which the jar-content should be extracted.
     * @throws FileNotFoundException
     *             when the jarFile does not exist
     * @throws IOException
     *             when a file could not be written or the jar-file could not
     *             read.
     */
    public static void unjar(File jarFile, File targetDir)
            throws FileNotFoundException, IOException {
        // clear target directory:
        if (targetDir.exists()) {
            targetDir.delete();
        }
        // create new target directory:
        targetDir.mkdirs();
        // read jar-file:
        String targetPath = targetDir.getAbsolutePath() + File.separatorChar;
        byte[] buffer = new byte[1024 * 1024];
        JarFile input = new JarFile(jarFile, false, ZipFile.OPEN_READ);
        Enumeration<JarEntry> enumeration = input.entries();
        for (; enumeration.hasMoreElements();) {
            JarEntry entry = enumeration.nextElement();
            if (!entry.isDirectory()) {
                // do not copy anything from the package cache:
                if (entry.getName().indexOf("package cache") == -1) {
                    String path = targetPath + entry.getName();
                    File file = new File(path);
                    if (!file.getParentFile().exists()) {
                        file.getParentFile().mkdirs();
                    }
                    FileOutputStream out = new FileOutputStream(file);
                    InputStream in = input.getInputStream(entry);
                    int read;
                    while ((read = in.read(buffer)) != -1) {
                        out.write(buffer, 0, read);
                    }
                    in.close();
                    out.close();
                }
            }
        }

    }

    /**
     * Extracts the given resource from a jar-file to the specified directory.
     * 
     * @param jarFile
     *            The jar file which should be unpacked
     * @param resource
     *            The name of a resource in the jar
     * @param targetDir
     *            The directory to which the jar-content should be extracted.
     * @throws FileNotFoundException
     *             when the jarFile does not exist
     * @throws IOException
     *             when a file could not be written or the jar-file could not
     *             read.
     */
    public static void unjar(File jarFile, String resource, File targetDir)
            throws FileNotFoundException, IOException {
        // clear target directory:
        if (targetDir.exists()) {
            targetDir.delete();
        }
        // create new target directory:
        targetDir.mkdirs();
        // read jar-file:
        String targetPath = targetDir.getAbsolutePath() + File.separatorChar;
        byte[] buffer = new byte[1024 * 1024];
        JarFile input = new JarFile(jarFile, false, ZipFile.OPEN_READ);
        Enumeration<JarEntry> enumeration = input.entries();
        for (; enumeration.hasMoreElements();) {
            JarEntry entry = enumeration.nextElement();
            if (!entry.isDirectory()) {
                // do not copy anything from the package cache:
                if (entry.getName().equals(resource)) {
                    String path = targetPath + entry.getName();
                    File file = new File(path);
                    if (!file.getParentFile().exists()) {
                        file.getParentFile().mkdirs();
                    }
                    FileOutputStream out = new FileOutputStream(file);
                    InputStream in = input.getInputStream(entry);
                    int read;
                    while ((read = in.read(buffer)) != -1) {
                        out.write(buffer, 0, read);
                    }
                    in.close();
                    out.close();
                }
            }
        }
    }

    /**
     * Reads the package-names from the given jar-file.
     * 
     * @param jarFile
     *            the jar file
     * @return an array with all found package-names
     * @throws IOException
     *             when the jar-file could not be read
     */
    public static String[] getPackageNames(File jarFile) throws IOException {
        HashMap<String, String> packageNames = new HashMap<String, String>();
        JarFile input = new JarFile(jarFile, false, ZipFile.OPEN_READ);
        Enumeration<JarEntry> enumeration = input.entries();
        for (; enumeration.hasMoreElements();) {
            JarEntry entry = enumeration.nextElement();
            String name = entry.getName();
            if (name.endsWith(".class")) {
                int endPos = name.lastIndexOf('/');
                boolean isWindows = false;
                if (endPos == -1) {
                    endPos = name.lastIndexOf('\\');
                    isWindows = true;
                }
                name = name.substring(0, endPos);
                name = name.replace('/', '.');
                if (isWindows) {
                    name = name.replace('\\', '.');
                }
                packageNames.put(name, name);
            }
        }
        return (String[]) packageNames.values().toArray(
                new String[packageNames.size()]);
    }
}

Porem não consigo utilizar, ao tentar adicionar o arquivo recebo a mensagem de erro:

java.util.zip.ZipException: invalid entry compressed size (expected 2775 but got 2776 bytes)
	at java.util.zip.ZipOutputStream.closeEntry(ZipOutputStream.java:206)
	at modinstaller.JarUtil.add(JarUtil.java:142)
	at modinstaller.JarUtil.addToJar(JarUtil.java:182)

Estou utilizando o metodo:

addToJar(File novo, File jar, null, true)

Tanto o arquivo novo quanto o jar estão corretos, ao rodar o [color=darkblue].exists()[/color] ambos retornam true e ao rodar um [color=blue]getPath()[/color] retorna o caminho correto para a localização do arquivo.
Alguem tem alguma ideia?

File novo = new File("c:\\teste\\novo.txt");
File jar = new File("c:\\teste\\teste.jar");
addToJar(File novo, File jar, null, true);

O erro é causado ali na linha 140 [color=red]out.closeEntry()[/color] que causa o errro na linha 180 [color=red]add(entry, entryIn, out, crc, buffer);[/color]

Olá!

Veja se é isso que vc quer:

[code]public class ResourceLoader {

private File directory;
private ClassLoader loader;

private ResourceLoader(File dir) {
	this.directory = dir;
}

/**
 * Create a ResourceLoader for a specific file system location.
 * All JAR files and subdirectories in the location will be added
 * to the classpath
 */
public static ClassLoader createForDirectory(File dir) {
	ResourceLoader loader = null;
	if (dir.isDirectory()) {
		loader = new ResourceLoader(dir);
	}
	loader.addJarsToPath();
	return loader.getClassLoader();
}

public static ClassLoader createForDirectory(String dir) {
	File f = new File(dir);
	return createForDirectory(f);
}

private File[] addJarsToPath() {
	File[] jarFiles = directory.listFiles();

	List<URL> urlList = new ArrayList<URL>();
	for (File f : jarFiles) {
		try {
			urlList.add(f.toURI().toURL());
		} catch (MalformedURLException ex) {
		}
	}
	ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
	URL[] urls = new URL[urlList.size()];
	urls = urlList.toArray(urls);
	URLClassLoader classLoader = new URLClassLoader(urls, parentLoader);
	this.loader = classLoader;

	return jarFiles;
}

public ClassLoader getClassLoader() {
	return this.loader;
}

}[/code]

Esse código adiciona Jars ao classpath da sua aplicação.

Até mais

Opa Eliangela. Obrigado pela resposta, porem este codigo, como disse, adiciona JARS ao CLASSPATH, eu preciso adiconar arquivos ao jar.
Na verdade eu até consegui com a classe a baixo. Mas como nada é perfeito, ela não inclui diretorio no arquivo. Consegui até uma outra que adiciona diretorio, so que esta por sua vez, apaga o jar e cria outro novo. Estou tentando entender como funcionam pra criar uma que adicione um arquivo especifico, e se for um diretorio, faz recursivamente para todos dentro. Mas confesso, não estou entendendo muita coisa.

Classe que adiciona um arquivo específico.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class Jar {
    public static void addFilesToExistingZip(File zipFile,
            File[] files) throws IOException {
        // get a temp file
        File tempFile = File.createTempFile(zipFile.getName(), null);
        // delete it, otherwise you cannot rename your existing zip to it.
        tempFile.delete();
        boolean renameOk = zipFile.renameTo(tempFile);
        if (!renameOk) {
            throw new RuntimeException("could not rename the file " + zipFile.getAbsolutePath() + " to " + tempFile.getAbsolutePath());
        }
        byte[] buf = new byte[1024];
        ZipInputStream zin = new ZipInputStream(new FileInputStream(tempFile));
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile));
        ZipEntry entry = zin.getNextEntry();
        while (entry != null) {
            String name = entry.getName();
            boolean notInFiles = true;
            for (File f : files) {
                if (f.getName().equals(name)) {
                    notInFiles = false;
                    break;
                }
            }
            if (notInFiles) {
                // Add ZIP entry to output stream.
                out.putNextEntry(new ZipEntry(name));
                // Transfer bytes from the ZIP file to the output file
                int len;
                while ((len = zin.read(buf)) > 0) {
                    out.write(buf, 0, len);
                }
            }
            entry = zin.getNextEntry();
        }
        // Close the streams		
        zin.close();
        // Compress the files
        for (int i = 0; i < files.length; i++) {
            InputStream in = new FileInputStream(files[i]);
            // Add ZIP entry to output stream.
            out.putNextEntry(new ZipEntry(files[i].getName()));
            // Transfer bytes from the file to the ZIP file
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
            // Complete the entry
            out.closeEntry();
            in.close();
        }
        // Complete the ZIP file
        out.close();
        tempFile.delete();
    }
}

Classe que cria um jar novo com diretorio.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipUsingJavaUtil {
    /*
     * Zip function zip all files and folders
     */
    @SuppressWarnings("finally")
    public boolean zipFiles(String srcFolder, String destZipFile) {
        boolean result = false;
        try {
            System.out.println("Program Start zipping the given files");
            /*
             * send to the zip procedure
             */
            zipFolder(srcFolder, destZipFile);
            result = true;
            System.out.println("Given files are successfully zipped");
        } catch (Exception e) {
            System.out.println("Some Errors happned during the zip process");
        } finally {
            return result;
        }
    }
    /*
     * zip the folders  
     */
    public void zipFolder(String srcFolder, String destZipFile) throws Exception {
        ZipOutputStream zip = null;
        FileOutputStream fileWriter = null;
        /*
         * create the output stream to zip file result
         */
        fileWriter = new FileOutputStream(destZipFile);
        zip = new ZipOutputStream(fileWriter);
        /*
         * add the folder to the zip
         */
        addFolderToZip("", srcFolder, zip);
        /*
         * close the zip objects
         */
        zip.flush();
        zip.close();
    }
    /*
     * recursively add files to the zip files
     */
    public void addFileToZip(String path, String srcFile, ZipOutputStream zip, boolean flag) throws Exception {
        /*
         * create the file object for inputs  
         */
        File folder = new File(srcFile);
        /*
         * if the folder is empty add empty folder to the Zip file
         */
        if (flag == true) {
            zip.putNextEntry(new ZipEntry(path + "/" + folder.getName() + "/"));
        } else {     /*
             * if the current name is directory, recursively traverse it to get the files
             */
            if (folder.isDirectory()) {
                /*
                 * if folder is not empty 
                 */
                addFolderToZip(path, srcFile, zip);
            } else {
                /*
                 * write the file to the output
                 */
                byte[] buf = new byte[1024];
                int len;
                FileInputStream in = new FileInputStream(srcFile);
                zip.putNextEntry(new ZipEntry(path + "/" + folder.getName()));
                while ((len = in.read(buf)) > 0) {
                    /*
                     * Write the Result 
                     */
                    zip.write(buf, 0, len);
                }
            }
        }
    }
    /*
     * add folder to the zip file 
     */
    public void addFolderToZip(String path, String srcFolder, ZipOutputStream zip)
            throws Exception {
        File folder = new File(srcFolder);
        /*
         * check the empty folder
         */
        if (folder.list().length == 0) {
            System.out.println(folder.getName());
            addFileToZip(path, srcFolder, zip, true);
        } else {
            /*
             * list the files in the folder 
             */
            for (String fileName : folder.list()) {
                if (path.equals("")) {
                    addFileToZip(folder.getName(), srcFolder + "/" + fileName, zip, false);
                } else {
                    addFileToZip(path + "/" + folder.getName(), srcFolder + "/" + fileName, zip, false);
                }
            }
        }
    }
}

Aqui como eu fiz para executar a segunda classe. Funciona, mas como disse, gera um novo.

ZipUsingJavaUtil jar = new ZipUsingJavaUtil();
ZipOutputStream zip = null;
FileOutputStream fileWriter = null;
/*
* create the output stream to zip file result
*/
fileWriter = new FileOutputStream(jarExistente.getAbsolutePath());
zip = new ZipOutputStream(fileWriter);
jar.addFileToZip("", "pasta", zip, true);

Primariamente esta segunda classe veio com seus metodos [color=red]private[/color], exceto o [color=blue]zipFiles[/color]. Porem apos testar e ver que ele apagava o meu jar atual e criava um novo, eu alterei os metodos para public na tentativa de executar diretamente o [color=darkblue]addFileToZip[/color], mas sem sucesso.
Até tentei usar esta classe que passou, mas ela so tem uma entrada, no caso eu precisaria de duas uma para o(s) arquivos e uma para o jar de destino.
Estou tentando juntar as 3 para ver se consigo algo. Qualquer sujestão é sempre bem vinda.

Não sei se tem algo relacionado, deve ter, mas estas classes são para trabalhar com ZIP quando na verdade estou trabalhando com jar. Porem eu até axei alguns exemplos com jar, mas eles nem compilaram. Essa de zip funciona, tirando a parte que apaga meu atual :?

vc pode fazer assim:

Descompacte os arquivos para uma pasta temporária, faça as alterações que vc precisar (alterar, excluir arquivos, criar pastas), daí vc compacta de novo.

Na verdade, os leitores de arquivos ZIP fazem desta maneira, pois é muito complicado vc fazer todas essas coisas direto dentro de um arquivo.

Desculpe, eu não tinha lido a parte de baixo do seu post.

O procedimento é este mesmo. Tem que descompactar, fazer as alterações e depois compactar um novo arquivo.

As classes estão corretas, porque o JAR é um ZIP com a extensão alterada.