< 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,39 ****
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.Objects;
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.
--- 27,43 ----
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.ZipFile;
import jdk.tools.jlink.internal.Archive.Entry.EntryType;
/**
* An Archive backed by a jar file.
*** 41,57 ****
public abstract class JarArchive implements Archive {
/**
* An entry located in a jar file.
*/
! private class JarEntry extends Entry {
private final long size;
! private final ZipEntry entry;
! private final ZipFile file;
! JarEntry(String path, String name, EntryType type, ZipFile file, ZipEntry entry) {
super(JarArchive.this, path, name, type);
this.entry = Objects.requireNonNull(entry);
this.file = Objects.requireNonNull(file);
size = entry.getSize();
}
--- 45,61 ----
public abstract class JarArchive implements Archive {
/**
* An entry located in a jar file.
*/
! private class JarFileEntry extends Entry {
private final long size;
! private final JarEntry entry;
! private final JarFile file;
! 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,83 ****
public InputStream stream() throws IOException {
return file.getInputStream(entry);
}
}
private static final String MODULE_INFO = "module-info.class";
private final Path file;
private final String moduleName;
! // currently processed ZipFile
! private ZipFile zipFile;
protected JarArchive(String mn, Path file) {
Objects.requireNonNull(mn);
Objects.requireNonNull(file);
this.moduleName = mn;
--- 72,92 ----
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 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,143 ****
}
@Override
public Stream<Entry> entries() {
try {
! if (zipFile == null) {
open();
}
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
! return zipFile.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();
String fn = getFileName(name);
! if (ze.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);
}
@Override
public void close() throws IOException {
! if (zipFile != null) {
! zipFile.close();
}
}
@Override
public void open() throws IOException {
! if (zipFile != null) {
! zipFile.close();
}
! zipFile = new ZipFile(file.toFile());
}
}
--- 104,236 ----
}
@Override
public Stream<Entry> entries() {
try {
! if (jarFile == null) {
open();
}
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
! 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);
! // 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 (je.isDirectory() || fn.startsWith("_")) {
return null;
}
EntryType rt = toEntryType(name);
if (fn.equals(MODULE_INFO)) {
fn = moduleName + "/" + MODULE_INFO;
}
! 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 (jarFile != null) {
! jarFile.close();
}
}
@Override
public void open() throws IOException {
! if (jarFile != null) {
! jarFile.close();
}
! // 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 >