10

As per the link : https://stackoverflow.com/a/1281295/1794012

I followed the instructions and created a jar file, the input directory where the source files for creating the jar is as follows,

  • so (directory)
  • so/some.txt (file)

when i am traversing through JarFile#entries method it is printing the following,

JarFile#entries output when jar created via JarOutputStream

META-INF/MANIFEST.MF
D:/so/
D:/so/some.txt

but i created the jar file using jar tool

Created jar using simple jar tool

jar -cvf so_commond.jar so so/some.txt
added manifest
adding: so/(in = 0) (out= 0)(stored 0%)
adding: so/some.txt(in = 7) (out= 9)(deflated -28%)

Now i use JarFile#entries to iterate the entries, the following is the output

JarFile#entries output when jar is created by jar tool

META-INF/ (this is not there when jar created by JarOutputStream)
META-INF/MANIFEST.MF
so/
so/some.txt

Could you please explain why the jar entry META-INF is shown only when jar is created by jar tool and does not shown when jar is created by JarOutputStream?

Code:

public static void main(String[] args){
  run();
    for(Enumeration<JarEntry> e = jf.entries(); e.hasMoreElements();){
                       System.out.println(e.nextElement().getName());
        }
 }

 public static void run() throws IOException
    {
        Manifest manifest = new Manifest();
        manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
        JarOutputStream target = new JarOutputStream(new FileOutputStream("D:\\so.jar"),
                  manifest);
        add(new File("D:\\so"), target);
        target.close();
    }

    private static 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();
        }
    }
Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
chebus
  • 762
  • 1
  • 8
  • 26

3 Answers3

2

Directories are optional in the zip file format. When using the zip command line tool this can be configured with --no-dir-entries.

Jesse Wilson
  • 39,078
  • 8
  • 121
  • 128
  • man zip says of `-D`/`--no-dir-entries`: "Do not create entries in the zip archive for directories. Directory entries are created by default so that their attributes can be saved in the zip archive." So I guess it's just a matter of whether I'm interested in the directory attributes (I'm not). – Tom Hawtin - tackline Nov 17 '21 at 17:18
  • 1
    (If I didn't recognise the name, I'd have awarded you the bounty.) – Tom Hawtin - tackline Nov 17 '21 at 17:24
2

To get a result similar to that produced by the jar -cvf so_commond.jar so so/some.txt command , you can change the run method in this way:

    public static void run() throws IOException {
    Manifest manifest = new Manifest();
    manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
    try ( JarOutputStream target = new JarOutputStream(new FileOutputStream("D:\\so.jar"))) {
        if (manifest != null) {
            ZipEntry e = new ZipEntry(MANIFEST_DIR);
            e.setTime(System.currentTimeMillis());
            e.setSize(0);
            e.setCrc(0);
            target.putNextEntry(e);
            e = new ZipEntry(MANIFEST_NAME);
            e.setTime(System.currentTimeMillis());
            target.putNextEntry(e);
            manifest.write(target);
            target.closeEntry();
        }
    add(new File("D:\\so"), target);
    target.close();
    }
}

the above code require the following definitions:

    import static java.util.jar.JarFile.MANIFEST_NAME;
    // .. and in the Class
    static final String MANIFEST_DIR = "META-INF/";

The reason is that the jar tool has a create method that act this way: https://github.com/geekerstar/OpenJdk-11/blob/a576af6f90dfa05a935b813d633d9c8aaa690f83/src/sun/tools/jar/Main.java#L847

 /**
 * Creates a new JAR file.
 */
void create(OutputStream out, Manifest manifest) throws IOException
{
    try (ZipOutputStream zos = new JarOutputStream(out)) {
        if (flag0) {
            zos.setMethod(ZipOutputStream.STORED);
        }
        // TODO: check module-info attributes against manifest ??
        if (manifest != null) {
            if (vflag) {
                output(getMsg("out.added.manifest"));
            }
            ZipEntry e = new ZipEntry(MANIFEST_DIR);
            e.setTime(System.currentTimeMillis());
            e.setSize(0);
            e.setCrc(0);
            zos.putNextEntry(e);
            e = new ZipEntry(MANIFEST_NAME);
            e.setTime(System.currentTimeMillis());
            if (flag0) {
                crc32Manifest(e, manifest);
            }
            zos.putNextEntry(e);
            manifest.write(zos);
            zos.closeEntry();
        }
        updateModuleInfo(moduleInfos, zos);
        for (Entry entry : entries) {
            addFile(zos, entry);
        }
    }
}
Franco Rondini
  • 10,841
  • 8
  • 51
  • 77
  • 1
    It seems that `jar` includes directories if the shell expansion lists them explicitly, which isn't necessarily obvious if you're not looking for it (IMO). I was kind of curious as to *why* you would want directory entries (but wasn't clear on that). – Tom Hawtin - tackline Nov 17 '21 at 17:24
0

When you use the command-line tool you will always have two separate entries. And when using the JarOutputStream class, you will always have one. Here is why:

On one side, if you look into the JarOutputStream class, you can find that this tool uses a single entry for META-INF/MANIFEST.MF:

    public JarOutputStream(OutputStream out, Manifest man) throws IOException {
        super(out);
        if (man == null) {
            throw new NullPointerException("man");
        } else {
            ZipEntry e = new ZipEntry("META-INF/MANIFEST.MF");
            this.putNextEntry(e);
            man.write(new BufferedOutputStream(this));
            this.closeEntry();
        }
    }

On the other side, the jar command line tool, as stated by Franco Rondini in his answer, inserts two separate entries, one for META-INF directory, and one for MANIFEST.MF file:

https://github.com/geekerstar/OpenJdk-11/blob/a576af6f90dfa05a935b813d633d9c8aaa690f83/src/sun/tools/jar/Main.java#L847

    void create(OutputStream out, Manifest manifest) throws IOException
    {
        try (ZipOutputStream zos = new JarOutputStream(out)) {
            if (flag0) {
                zos.setMethod(ZipOutputStream.STORED);
            }
            // TODO: check module-info attributes against manifest ??
            if (manifest != null) {
                if (vflag) {
                    output(getMsg("out.added.manifest"));
                }
                ZipEntry e = new ZipEntry(MANIFEST_DIR); //<<<<<<<<<<<< HERE
                e.setTime(System.currentTimeMillis());
                e.setSize(0);
                e.setCrc(0);
                zos.putNextEntry(e);
                e = new ZipEntry(MANIFEST_NAME); //<<<<<<<<<<<<<<<<<<<< AND HERE
                e.setTime(System.currentTimeMillis());
                if (flag0) {
                    crc32Manifest(e, manifest);
                }
                zos.putNextEntry(e);
                manifest.write(zos);
                zos.closeEntry();
            }
            updateModuleInfo(moduleInfos, zos);
            for (Entry entry : entries) {
                addFile(zos, entry);
            }
        }
    }

Hence the difference between the two.

gmanjon
  • 1,483
  • 1
  • 12
  • 16