# Jar in Eclipse nicht nutzbar



## gorgophol (5. Mai 2009)

Hallo,

ich habe folgendes Phänomen:
Ich packe per java mehrere Jars zu einem einzigen zusammen. Dazu nutzte ich ursprünglich den JarOutputStream (inzwischen auch ZipOutputStream ...). Das Ergebnis ist leider ernüchternd und zwar aus folgendem Grund:

Wird das neue Jar dann in Eclipse eingebunden (es soll also als Referrenced Library verwendet werden), wird es nicht als Java-Archiv erkannt und dementsprechend werden auch die beinhalteten Klassen nicht gefunden und können nicht genutzt werden. Entpacke ich das jar (zb mit WinZip) und packe es sofort wieder (ohne etwas zu ändern) funktioniert es. Offensichtlich liegt es also an meiner Art zu packen.

Die Ursprungs-Jars funktionieren übrigens alle problemlos.
Kann mir jemand einen Tipp geben, wo das Problem liegen könnte bzw wie ich mit java ein Jar erzeuge, das später auch in Eclipse verwendet werden kann?

Vielen Dank.

Gruß,
Gorgophol


----------



## Ebenius (6. Mai 2009)

Was kommt denn, wenn Du auf der Kommandozeile das da aufrufst: 
	
	
	
	





```
jar tf YOUR_JAR_FILE
```
Wird der Inhalt richtig angezeigt?

Ich hab das eben mal probiert. Voraussetzung: Dieses Programm läuft in einem Verzeichnis unterhalb dessen ein "bin"-Verzeichnis mit dem Klassenbaum seiner selbst liegt (übliches Eclipse-Java-Projekt also): 
	
	
	
	





```
/* (@)JarSelfExporterTest.java */

/* Copyright 2009 Sebastian Haufe

 * Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       [url]http://www.apache.org/licenses/LICENSE-2.0[/url]

 * Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. */

package com.ebenius;

import java.io.*;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;

public class JarSelfExporterTest {

  public static class NestedClass {}

  public static void main(String[] args)
        throws FileNotFoundException, IOException {
    final File classFolder = new File("bin").getAbsoluteFile();

    final Class<JarSelfExporterTest> myClass = JarSelfExporterTest.class;
    final String myClassName = myClass.getSimpleName();
    final Package myPackage = myClass.getPackage();
    final String myPackageName = myPackage.getName();
    final String myPackageFileName = myPackageName.replace('.', '/');
    final File pkgFile = new File(classFolder, myPackageFileName);

    final File[] classFiles = pkgFile.listFiles(new FilenameFilter() {

      public boolean accept(File dir, String name) {
        return name.equals(myClassName + ".class")
              || name.startsWith(myClassName + '$')
              && name.endsWith(".class");
      }
    });

    final String jarFileName = myClassName + ".jar";
    final FileOutputStream fos = new FileOutputStream(jarFileName);
    final JarOutputStream os = new JarOutputStream(fos);
    for (File f : classFiles) {
      final String name = myPackageFileName + '/' + f.getName();
      System.out.printf("Adding: %s", name);
      System.out.println();
      final JarEntry entry = new JarEntry(name);
      os.putNextEntry(entry);
      final InputStream is = new BufferedInputStream(new FileInputStream(f));
      int aByte;
      while ((aByte = is.read()) != -1) {
        os.write(aByte);
      }
    }
    os.flush();
    os.close();
    System.out.println("----------------------------");
    System.out.printf("Wrote: %s", jarFileName);
  }
}
```
Die Ausgabe sieht so aus: 
	
	
	
	





```
Adding: com/ebenius/JarSelfExporterTest$1.class
Adding: com/ebenius/JarSelfExporterTest.class
Adding: com/ebenius/JarSelfExporterTest$NestedClass.class
----------------------------
Wrote: JarSelfExporterTest.jar
```
_jar_ kann das Archiv richtig auflisten: 
	
	
	
	





```
$ jar tf JarSelfExporterTest.jar
com/ebenius/JarSelfExporterTest$1.class
com/ebenius/JarSelfExporterTest.class
com/ebenius/JarSelfExporterTest$NestedClass.class
```
_less_ gibt noch etwas genauer die Struktur aus: 
	
	
	
	





```
$ less JarSelfExporterTest.jar

Archive:  ./JarSelfExporterTest.jar
 Length   Method    Size  Ratio   Date   Time   CRC-32    Name
--------  ------  ------- -----   ----   ----   ------    ----
    1196  Defl:N      638  47%  05-06-09 15:35  b4a0150e  com/ebenius/JarSelfExporterTest$1.class
    3089  Defl:N     1617  48%  05-06-09 15:35  29972129  com/ebenius/JarSelfExporterTest.class
     412  Defl:N      256  38%  05-06-09 15:35  14be0b54  com/ebenius/JarSelfExporterTest$NestedClass.class
--------          -------  ---                            -------
    4697             2511  47%                            3 files
```
Und Eclipse frisst das Archiv problemlos.

Ebenius


----------



## gorgophol (7. Mai 2009)

Danke für die erste Antwort überhaupt  

Also in der Kommandozeile wird der Jar komplett richtig aufgelistet. Wie gesagt an sich scheint das völlig korrekt zu sein. Außer der Eigenheit, dass im Eclipse, wenn ichs als Library "aufblätter" nicht die packages kommen, sondern ne normale Verzeichnisstruktur ... 

Ich hab inzwischen gemerkt, dass der repack-Vorgang mit Winzip dazu führt, dass das Jar etwa 1 KB größer wird. Schreibt WinZip da irgendwelche Meta-Informationen rein die Java weglässt? 

Deine Klasse kann ich leider nicht nutzen. Das ganze läuft nicht auf einem Eclipse-Projekt, sondern auf einem Verzeichnis auf dem Build-Server und innerhalb eines selbstgeschriebenen Maven-Plugins. 

Hier die Klasse die ich für die Jar-Packung verwende. Vielleicht ist da ja ein Fehler drin?


```
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

/**
 * This class implements a "jar cf" command - you can create a jar file from
 * files and directories on your system using this class.
 */
public class JarWriter {
    /**
     * This exception class is thrown if the target jar file provided to
     * JarWriter already exists.
     */
    public class TargetAlreadyExists extends IOException {
        public TargetAlreadyExists(String s) {
            super(s);
        }
    }

    /**
     * Creates an instance you can use to write files and directories to a jar
     * file.
     * 
     * @param targetJar
     *            a File indicating where to write the jar file to. Should not
     *            exist.
     * @param manifest
     *            a Manifest for the jar file, or null to construct one
     *            automatically(?).
     * @param comment
     *            a comment for the jar file, or null for none.
     * @throws IOException
     *             if there is any problem, including
     *             JarWriter$TargetAlreadyExists.
     */
    public JarWriter(File targetJar, Manifest manifest, String comment)
            throws IOException {
        if (targetJar.exists()) {
            // TODO von mir eingebaut zum testen was passiert
            System.out.println("Lösche " + targetJar.getName());
            targetJar.delete();
            // throw new TargetAlreadyExists(targetJar.toString());
        }
        // if (manifest != null) {
        // // Create a Jar output stream with an explicit manifest
        // fJarOutputStream= new JarOutputStream(new
        // FileOutputStream(targetJar), manifest);
        // }
        // else {
        // Create a Jar output stream with a default manifest(?)
        fJarOutputStream = new JarOutputStream(new FileOutputStream(targetJar));
        System.out
                .println("ZIP-Out-Erstellung: " + fJarOutputStream.toString());
        // }
        if (comment != null) {
            fJarOutputStream.setComment(comment);
        }
    }

    /**
     * Closes the archive and does all required cleanup.
     * 
     * @throws IOException
     *             to signal any unusal termination.
     */
    public void close() throws IOException {
        if (fJarOutputStream != null) {
            fJarOutputStream.flush();
            fJarOutputStream.close();
        }
    }

    /**
     * Writes the passed File to the current archive. If the File is a
     * directory, writes all its contents to the archive, recursively. Never
     * recurses into a directory called META-INF - that has to be written
     * specially.
     * 
     * @param file
     *            the file or directory to be written
     * @param destinationPath
     *            the path for the file inside the archive. Should be a relative
     *            path string and may use system-dependent directory separator
     *            or the jar-standard forward slash.
     * @throws IOException
     *             to signal any unusal termination.
     */
    public void write(File file, String destinationDir, boolean initialDirectory)
            throws IOException {
        if (file.isDirectory()) {
            File[] contents = file.listFiles();

            // If this directory is called META-INF then strip any
            // member file called MANIFEST.MF ... the manifest is managed
            // separately.
            // "Strip" the member by nulling out its name; later we check for
            // null names.
            // if (file.getName().equals("META-INF")) {
            // for (int j = 0; j < contents.length; j++) {
            // if (contents[j].equals("MANIFEST.MF")) {
            // contents[j] = null;
            // }
            // }
            // }

            // The target subdirectory name in the jar file depends on
            // the initialDirectory flag. If true (as when we are first called),
            // then we use the destinationDir as the target dir.
            // There is a special case in the file loop for the empty string.
            //
            // If initialDirectory is false (as when we have done recursion),
            // we append file.getName() to the destinationDir to get the new
            // destination dir, with a slash separator if the old destination
            // dir
            // wasn't the empty string.

            String subdirName;
            if (initialDirectory) {
                subdirName = destinationDir;
            } else {
                if (destinationDir.equals("")) {
                    subdirName = file.getName();
                } else {
                    subdirName = destinationDir + "/" + file.getName();
                }
            }
            for (int i = 0; i < contents.length; i++) {
                // contents[i] might be null if it was MANIFEST.MF - see above
                if (contents[i] != null) {
                    write(contents[i], subdirName, false);
                }
            }
        } else {
            // Read the contents of this file into a buffer, then
            // use the byte[] form of write() to write to the jar stream.
            ByteArrayOutputStream output = null;
            BufferedInputStream contentStream = null;

            try {
                output = new ByteArrayOutputStream();
                contentStream = new BufferedInputStream(new FileInputStream(
                        file));
                // The byte array will probably be allocated big enough to hold
                // the whole file,
                // assuming available() on a file input stream returns the size
                // of the file.
                // Just in case it returns something stupid like '1', we have a
                // 4K minimum.
                int chunkSize = Math.max(contentStream.available(), 4096);
                byte[] readBuffer = new byte[chunkSize];
                int count;
                while ((count = contentStream.read(readBuffer, 0, chunkSize)) != -1) {
                    output.write(readBuffer, 0, count);
                }
            } finally {
                if (output != null)
                    output.close();
                if (contentStream != null)
                    contentStream.close();
            }

            long lastModified = file.lastModified();
            String destinationFile;
            if (destinationDir == "") {
                destinationFile = file.getName();
            } else {
                destinationFile = destinationDir + "/" + file.getName();
            }
            write(destinationFile, output.toByteArray(), lastModified);
        }
    }

    /**
     * Creates a new JarEntry with the passed path and contents, and writes it
     * to the current archive.
     * 
     * @param destinationPath
     *            the path inside the archive to the file, with slashes.
     * @param contents
     *            the bytes to write
     * @param lastModified
     *            a long which represents the last modification date
     * @throws IOException
     *             if an I/O error has occurred
     */
    public void write(String destinationPath, byte[] contents, long lastModified)
            throws IOException {
        JarEntry newEntry = new JarEntry(destinationPath.replace(
                File.separatorChar, '/'));
        newEntry.setMethod(JarEntry.DEFLATED);
        // Set modification time
        newEntry.setTime(lastModified);

        fJarOutputStream.putNextEntry(newEntry);
        fJarOutputStream.write(contents);
        fJarOutputStream.flush();
    }

    private JarOutputStream fJarOutputStream;

}
```

Hab schon an diversen Stellen geschraubt, aber nix hat Erfolg gehabt ...


----------



## Ebenius (7. Mai 2009)

Lass mal die Zeilen 196 bis 198 raus. Wenn das auch nicht hilft, dann erzeuge mal eine JAR-Datei mit dem Programm und hefte sie an den nächsten Beitrag.

Ebenius


----------



## gorgophol (7. Mai 2009)

Hat leider nichts geändert. 

Das Jar hab ich angehängt. 

Danke und Gruß, 
Gorgophol 

BTW auch aus Mannheim ;-)


----------



## Ebenius (7. Mai 2009)

_less_ spuckt mir dazu aus: 
	
	
	
	





```
Archive:  ./tmp/TestM3-1.0.jar
 Length   Method    Size  Ratio   Date   Time   CRC-32    Name
--------  ------  ------- -----   ----   ----   ------    ----
     226  Defl:N      169  25%  05-07-09 09:05  d2dd1b63  /de/inter/dvsv/test2/Test1.java
      54  Defl:N       54   0%  05-07-09 09:05  ea9d0b1a  /de/inter/dvsv/test2/Test2.java
      54  Defl:N       55  -2%  05-07-09 09:05  4cea00ae  /de/inter/dvsv/test2/Test3.java
      54  Defl:N       55  -2%  05-07-09 09:05  894d3e20  /de/inter/dvsv/test2/Test4.java
     280  Defl:N      209  25%  05-07-09 09:05  1400a5fd  /de/inter/dvsv/test2/Test3.class
     280  Defl:N      208  26%  05-07-09 09:05  1aef68d4  /de/inter/dvsv/test2/Test2.class
     579  Defl:N      359  38%  05-07-09 09:05  090112e9  /de/inter/dvsv/test2/Test1.class
     280  Defl:N      209  25%  05-07-09 09:05  3c8fc622  /de/inter/dvsv/test2/Test4.class
     549  Defl:N      339  38%  05-07-09 09:05  71695cf6  /de/inter/dvdv/test/Test1.class
     278  Defl:N      206  26%  05-07-09 09:05  bcccfe58  /de/inter/dvdv/test/Test2.class
     940  Defl:N      438  53%  05-07-09 09:05  a51228aa  /META-INF/maven/de.inter.dvsv.test2/TestM2/pom.xml
     108  Defl:N      104   4%  05-07-09 09:05  8690d248  /META-INF/maven/de.inter.dvsv.test2/TestM2/pom.properties
    1390  Defl:N      546  61%  05-07-09 09:05  62cdfa68  /META-INF/maven/de.inter.dvsv.test/TestM1/pom.xml
     107  Defl:N      102   5%  05-07-09 09:05  aef9f3c5  /META-INF/maven/de.inter.dvsv.test/TestM1/pom.properties
      21  Defl:N       23 -10%  05-07-09 09:05  3a30b959  /META-INF/MANIFEST.MF
--------          -------  ---                            -------
    5200             3076  41%                            15 files
```
Die negativen Ratio's sehen für mich komisch aus... Was aber eher das Problem sein wird: Die Einträge beginnen alle mit einem Slash. Der sollte da nicht sein.

_jar tf_ gibt das so aus: 
	
	
	
	





```
/de/inter/dvsv/test2/Test1.java
/de/inter/dvsv/test2/Test2.java
/de/inter/dvsv/test2/Test3.java
/de/inter/dvsv/test2/Test4.java
/de/inter/dvsv/test2/Test3.class
/de/inter/dvsv/test2/Test2.class
/de/inter/dvsv/test2/Test1.class
/de/inter/dvsv/test2/Test4.class
/de/inter/dvdv/test/Test1.class
/de/inter/dvdv/test/Test2.class
/META-INF/maven/de.inter.dvsv.test2/TestM2/pom.xml
/META-INF/maven/de.inter.dvsv.test2/TestM2/pom.properties
/META-INF/maven/de.inter.dvsv.test/TestM1/pom.xml
/META-INF/maven/de.inter.dvsv.test/TestM1/pom.properties
/META-INF/MANIFEST.MF
```
Bei meinen JAR-Dateien steht da aber: 
	
	
	
	





```
com/ebenius/JarSelfExporterTest$1.class
com/ebenius/JarSelfExporterTest.class
com/ebenius/JarSelfExporterTest$NestedClass.class
```
Probier's mal nach dem Neu-Einpacken mit Winzip (hab kein Windows). Da steht dann sicher alles ohne den Slash da.

Ebenius


----------



## gorgophol (7. Mai 2009)

Du bist genial 

Genau das wars. Ich musste nur den ersten Slash verhindern, dann funktionierts  

Vielen Dank, dass du dir die Zeit genommen hast, dir das anzuschauen. 
Jetzt kann ich endlich wieder weiter arbeiten  

Viele Grüße, 
Gorgophol


----------

