< prev index next >
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JarArchive.java
Print this page
rev 15454 : 8156499: Update jlink to support creating images with modules that are packaged as multi-release JARs
Reviewed-by:
Contributed-by: steve.drach@oracle.com
@@ -27,13 +27,17 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.Map;
import java.util.Objects;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
-import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import jdk.tools.jlink.internal.Archive.Entry.EntryType;
/**
* An Archive backed by a jar file.
@@ -41,17 +45,17 @@
public abstract class JarArchive implements Archive {
/**
* An entry located in a jar file.
*/
- private class JarEntry extends Entry {
+ private class JarFileEntry extends Entry {
private final long size;
- private final ZipEntry entry;
- private final ZipFile file;
+ private final JarEntry entry;
+ private final JarFile file;
- JarEntry(String path, String name, EntryType type, ZipFile file, ZipEntry entry) {
+ JarFileEntry(String path, String name, EntryType type, JarFile file, JarEntry entry) {
super(JarArchive.this, path, name, type);
this.entry = Objects.requireNonNull(entry);
this.file = Objects.requireNonNull(file);
size = entry.getSize();
}
@@ -68,16 +72,21 @@
public InputStream stream() throws IOException {
return file.getInputStream(entry);
}
}
+ private static final String MANIFEST = "META-INF/MANIFEST.MF";
private static final String MODULE_INFO = "module-info.class";
+ private static final String VERSIONS_DIR = "META-INF/versions/";
+ private static final int VERSIONS_DIR_LEN = VERSIONS_DIR.length();
private final Path file;
private final String moduleName;
- // currently processed ZipFile
- private ZipFile zipFile;
+ // currently processed JarFile
+ private JarFile jarFile;
+ private int version;
+ private int offset;
protected JarArchive(String mn, Path file) {
Objects.requireNonNull(mn);
Objects.requireNonNull(file);
this.moduleName = mn;
@@ -95,49 +104,133 @@
}
@Override
public Stream<Entry> entries() {
try {
- if (zipFile == null) {
+ if (jarFile == null) {
open();
}
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
- return zipFile.stream().map(this::toEntry).filter(n -> n != null);
+ if (jarFile.isMultiRelease()) {
+ // sort entries in ascending version order, extract the base name
+ // and add them to a map, then return the Entries as with regular jar
+ return jarFile.stream()
+ .filter(je -> !je.isDirectory())
+ .filter(je -> !je.getName().equals(MANIFEST))
+ .filter(this::versionAcceptable)
+ .sorted(entryComparator)
+ .collect(Collectors.toMap(this::extractBaseName, je -> je, (v1, v2) -> v2))
+ .entrySet()
+ .stream().map(this::toEntry).filter(n -> n != null);
+ }
+ return jarFile.stream().map(this::toEntry).filter(n -> n != null);
}
abstract EntryType toEntryType(String entryName);
abstract String getFileName(String entryName);
- private Entry toEntry(ZipEntry ze) {
- String name = ze.getName();
+ // sort base entries before versioned entries
+ private Comparator<JarEntry> entryComparator = (je1, je2) -> {
+ String s1 = je1.getName();
+ String s2 = je2.getName();
+ if (s1.equals(s2)) return 0;
+ boolean b1 = s1.startsWith(VERSIONS_DIR);
+ boolean b2 = s2.startsWith(VERSIONS_DIR);
+ if (b1 && !b2) return 1;
+ if (!b1 && b2) return -1;
+ int n = 0; // starting char for String compare
+ if (b1 && b2) {
+ // normally strings would be sorted so "10" goes before "9", but
+ // version number strings need to be sorted numerically
+ n = VERSIONS_DIR.length(); // skip the common prefix
+ int i1 = s1.indexOf('/', n);
+ int i2 = s1.indexOf('/', n);
+ if (i1 == -1) throw new RuntimeException(s1); // fixme, better message
+ if (i2 == -1) throw new RuntimeException(s2); // fixme, better message
+ // shorter version numbers go first
+ if (i1 != i2) return i1 - i2;
+ // otherwise, handle equal length numbers below
+ }
+ int l1 = s1.length();
+ int l2 = s2.length();
+ int lim = Math.min(l1, l2);
+ for (int k = n; k < lim; k++) {
+ char c1 = s1.charAt(k);
+ char c2 = s2.charAt(k);
+ if (c1 != c2) {
+ return c1 - c2;
+ }
+ }
+ return l1 - l2;
+ };
+
+ // must be invoked after versionAcceptable
+ private String extractBaseName(JarEntry je) {
+ String name = je.getName();
+ if (name.startsWith(VERSIONS_DIR)) {
+ return name.substring(VERSIONS_DIR_LEN + offset + 1);
+ }
+ return name;
+ }
+
+ private Entry toEntry(JarEntry je) {
+ String name = je.getName();
+ return toEntry(name, je);
+ }
+
+ private Entry toEntry(Map.Entry<String,JarEntry> entry) {
+ String name = entry.getKey();
+ JarEntry je = entry.getValue();
+ return toEntry(name, je);
+ }
+
+ private Entry toEntry(String name, JarEntry je) {
String fn = getFileName(name);
- if (ze.isDirectory() || fn.startsWith("_")) {
+ if (je.isDirectory() || fn.startsWith("_")) {
return null;
}
EntryType rt = toEntryType(name);
if (fn.equals(MODULE_INFO)) {
fn = moduleName + "/" + MODULE_INFO;
}
- return new JarEntry(ze.getName(), fn, rt, zipFile, ze);
+ return new JarFileEntry(name, fn, rt, jarFile, je);
+ }
+
+ // must be invoked before extractBaseName
+ private boolean versionAcceptable(JarEntry je) {
+ String name = je.getName();
+ if (name.startsWith(VERSIONS_DIR)) {
+ name = name.substring(VERSIONS_DIR_LEN);
+ offset = name.indexOf('/');
+ if (offset == -1)
+ throw new RuntimeException("");// fixme, better message
+ if (Integer.parseInt(name.substring(0, offset)) <= version) {
+ return true;
+ }
+ return false;
+ }
+ return true;
}
@Override
public void close() throws IOException {
- if (zipFile != null) {
- zipFile.close();
+ if (jarFile != null) {
+ jarFile.close();
}
}
@Override
public void open() throws IOException {
- if (zipFile != null) {
- zipFile.close();
+ if (jarFile != null) {
+ jarFile.close();
}
- zipFile = new ZipFile(file.toFile());
+ // open this way to determine if it's a multi-release jar
+ jarFile = new JarFile(file.toFile(), true, ZipFile.OPEN_READ, JarFile.runtimeVersion());
+ version = jarFile.getVersion().major();
}
}
< prev index next >