--- a/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java Fri Sep 30 10:52:19 2016 -0700
+++ b/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java Tue Oct 04 18:56:28 2016 -0700
@@ -56,6 +56,8 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import jdk.internal.jmod.JmodFile;
+import jdk.internal.jmod.JmodFile.Section;
import jdk.internal.module.ConfigurableModuleFinder;
import jdk.internal.perf.PerfCounter;
@@ -294,11 +296,11 @@
// -- jmod files --
- private Set<String> jmodPackages(ZipFile zf) {
- return zf.stream()
- .filter(e -> e.getName().startsWith("classes/") &&
- e.getName().endsWith(".class"))
- .map(e -> toPackageName(e.getName().substring(8)))
+ private Set<String> jmodPackages(JmodFile jf) {
+ return jf.stream()
+ .filter(e -> e.section() == Section.CLASSES)
+ .map(JmodFile.Entry::name)
+ .map(this::toPackageName)
.filter(pkg -> pkg.length() > 0) // module-info
.collect(Collectors.toSet());
}
@@ -311,14 +313,10 @@
* @throws InvalidModuleDescriptorException
*/
private ModuleReference readJMod(Path file) throws IOException {
- try (ZipFile zf = new ZipFile(file.toString())) {
- ZipEntry ze = zf.getEntry("classes/" + MODULE_INFO);
- if (ze == null) {
- throw new IOException(MODULE_INFO + " is missing: " + file);
- }
+ try (JmodFile jf = new JmodFile(file)) {
ModuleDescriptor md;
- try (InputStream in = zf.getInputStream(ze)) {
- md = ModuleDescriptor.read(in, () -> jmodPackages(zf));
+ try (InputStream in = jf.getInputStream(Section.CLASSES, MODULE_INFO)) {
+ md = ModuleDescriptor.read(in, () -> jmodPackages(jf));
}
return ModuleReferences.newJModModule(md, file);
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java Tue Oct 04 18:56:28 2016 -0700
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.jmod;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Helper class to read JMOD file
+ */
+public class JmodFile implements AutoCloseable {
+ // jmod magic number and version number
+ public static final int JMOD_MAJOR_VERSION = 0x01;
+ public static final int JMOD_MINOR_VERSION = 0x00;
+ public static final byte[] JMOD_MAGIC_NUMBER = {
+ 0x4A, 0x4D, /* JM */
+ JMOD_MAJOR_VERSION, JMOD_MINOR_VERSION, /* version 1.0 */
+ };
+
+ public static void checkMagic(Path file) throws IOException {
+ try (InputStream in = Files.newInputStream(file);
+ BufferedInputStream bis = new BufferedInputStream(in)) {
+ // validate the header
+ byte[] magic = new byte[4];
+ bis.read(magic);
+ if (magic[0] != JMOD_MAGIC_NUMBER[0] ||
+ magic[1] != JMOD_MAGIC_NUMBER[1]) {
+ throw new IOException("Invalid jmod file: " + file.toString());
+ }
+ if (magic[2] > JMOD_MAJOR_VERSION ||
+ (magic[2] == JMOD_MAJOR_VERSION && magic[3] > JMOD_MINOR_VERSION)) {
+ throw new IOException("Unsupported jmod version: " +
+ magic[2] + "." + magic[3] + " in " + file.toString());
+ }
+ }
+ }
+
+ /**
+ * JMOD sections
+ */
+ public static enum Section {
+ NATIVE_LIBS("native"),
+ NATIVE_CMDS("bin"),
+ CLASSES("classes"),
+ CONFIG("conf");
+
+ private final String jmodDir;
+ private Section(String jmodDir) {
+ this.jmodDir = jmodDir;
+ }
+
+ /**
+ * Returns the directory name in the JMOD file corresponding to
+ * this section
+ */
+ public String jmodDir() { return jmodDir; }
+
+ }
+
+ /**
+ * JMOD file entry.
+ *
+ * Each entry corresponds to a ZipEntry whose name is:
+ * Section::jmodDir + '/' + name
+ */
+ public static class Entry {
+ private final ZipEntry zipEntry;
+ private final Section section;
+ private final String name;
+
+ private Entry(ZipEntry e) {
+ String name = e.getName();
+ int i = name.indexOf('/');
+ if (i <= 1) {
+ throw new RuntimeException("invalid jmod entry: " + name);
+ }
+
+ this.zipEntry = e;
+ this.section = section(name);
+ this.name = name.substring(i+1);
+ }
+
+ /**
+ * Returns the section of this entry.
+ */
+ public Section section() {
+ return section;
+ }
+
+ /**
+ * Returns the name of this entry.
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the size of this entry.
+ */
+ public long size() {
+ return zipEntry.getSize();
+ }
+
+ public ZipEntry zipEntry() {
+ return zipEntry;
+ }
+
+ @Override
+ public String toString() {
+ return section.jmodDir() + "/" + name;
+ }
+
+ static Section section(String name) {
+ int i = name.indexOf('/');
+ String s = name.substring(0, i);
+ switch (s) {
+ case "native":
+ return Section.NATIVE_LIBS;
+ case "bin":
+ return Section.NATIVE_CMDS;
+ case "classes":
+ return Section.CLASSES;
+ case "conf":
+ return Section.CONFIG;
+ default:
+ throw new IllegalArgumentException("invalid section: " + s);
+ }
+ }
+ }
+
+ private final Path file;
+ private final ZipFile zipfile;
+
+ /**
+ * Constructs a {@code JmodFile} from a given path.
+ */
+ public JmodFile(Path file) throws IOException {
+ checkMagic(file);
+ this.file = file;
+ this.zipfile = new ZipFile(file.toFile());
+ }
+
+ /**
+ * Opens an {@code InputStream} for reading the named entry of the given
+ * section in this jmod file.
+ *
+ * @throws IOException if the named entry is not found, or I/O error
+ * occurs when reading it
+ */
+ public InputStream getInputStream(Section section, String name)
+ throws IOException
+ {
+
+ String entry = section.jmodDir() + "/" + name;
+ ZipEntry e = zipfile.getEntry(entry);
+ if (e == null) {
+ throw new IOException(name + " not found: " + file);
+ }
+ return zipfile.getInputStream(e);
+ }
+
+ /**
+ * Returns a stream of non-directory entries in this jmod file.
+ */
+ public Stream<Entry> stream() {
+ return zipfile.stream()
+ .filter(e -> !e.isDirectory())
+ .map(Entry::new);
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (zipfile != null) {
+ zipfile.close();
+ }
+ }
+}
--- a/jdk/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java Fri Sep 30 10:52:19 2016 -0700
+++ b/jdk/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java Tue Oct 04 18:56:28 2016 -0700
@@ -163,6 +163,16 @@
* be discarded.
*/
public void write(OutputStream out) throws IOException {
+ // emit to the output stream
+ out.write(toByteArray());
+ }
+
+ /**
+ * Returns the bytes of the modified module-info.class.
+ * Once this method has been called then the Extender object should
+ * be discarded.
+ */
+ public byte[] toByteArray() throws IOException {
ClassWriter cw
= new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
@@ -197,8 +207,7 @@
// add any attributes that didn't replace previous attributes
cv.finish();
- // emit to the output stream
- out.write(cw.toByteArray());
+ return cw.toByteArray();
}
/**
--- a/jdk/src/java.base/share/classes/module-info.java Fri Sep 30 10:52:19 2016 -0700
+++ b/jdk/src/java.base/share/classes/module-info.java Tue Oct 04 18:56:28 2016 -0700
@@ -124,6 +124,9 @@
jdk.jlink;
exports jdk.internal.jimage.decompressor to
jdk.jlink;
+ exports jdk.internal.jmod to
+ jdk.compiler,
+ jdk.jlink;
exports jdk.internal.logger to
java.logging;
exports jdk.internal.org.objectweb.asm to
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java Fri Sep 30 10:52:19 2016 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java Tue Oct 04 18:56:28 2016 -0700
@@ -55,6 +55,13 @@
private final Archive archive;
private final String path;
+ /**
+ * Constructs an entry of the given archive
+ * @param archive archive
+ * @param path
+ * @param name an entry name that does not contain the module name
+ * @param type
+ */
public Entry(Archive archive, String path, String name, EntryType type) {
this.archive = Objects.requireNonNull(archive);
this.path = Objects.requireNonNull(path);
@@ -62,25 +69,29 @@
this.type = Objects.requireNonNull(type);
}
- public Archive archive() {
+ public final Archive archive() {
return archive;
}
- public String path() {
- return path;
- }
-
- public EntryType type() {
+ public final EntryType type() {
return type;
}
- /*
+ /**
* Returns the name of this entry.
*/
- public String name() {
+ public final String name() {
return name;
}
+ /**
+ * Returns the name representing a ResourcePoolEntry in the form of:
+ * /$MODULE/$ENTRY_NAME
+ */
+ public final String getResourcePoolEntryName() {
+ return "/" + archive.moduleName() + "/" + name;
+ }
+
@Override
public String toString() {
return "type " + type.name() + " path " + path;
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/DirArchive.java Fri Sep 30 10:52:19 2016 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/DirArchive.java Tue Oct 04 18:56:28 2016 -0700
@@ -50,7 +50,7 @@
FileEntry(Path path, String name) {
super(DirArchive.this, getPathName(path), name,
- Archive.Entry.EntryType.CLASS_OR_RESOURCE);
+ Archive.Entry.EntryType.CLASS_OR_RESOURCE);
this.path = path;
try {
size = Files.size(path);
@@ -124,13 +124,7 @@
return null;
}
String name = getPathName(p).substring(chop);
- if (name.startsWith("_")) {
- return null;
- }
log.accept(moduleName + "/" + name);
- if (name.equals(MODULE_INFO)) {
- name = moduleName + "/" + MODULE_INFO;
- }
return new FileEntry(p, name);
}
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java Fri Sep 30 10:52:19 2016 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java Tue Oct 04 18:56:28 2016 -0700
@@ -40,6 +40,7 @@
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+
import jdk.tools.jlink.internal.Archive.Entry;
import jdk.tools.jlink.internal.Archive.Entry.EntryType;
import jdk.tools.jlink.internal.ResourcePoolManager.CompressedModuleData;
@@ -122,10 +123,6 @@
});
}
- public static boolean isClassPackage(String path) {
- return path.endsWith(".class") && !path.endsWith("module-info.class");
- }
-
public static void recreateJimage(Path jimageFile,
Set<Archive> archives,
ImagePluginStack pluginSupport)
@@ -265,26 +262,13 @@
return writer.getString(id);
}
});
+
for (Archive archive : archives) {
String mn = archive.moduleName();
- for (Entry entry : entriesForModule.get(mn)) {
- String path;
- if (entry.type() == EntryType.CLASS_OR_RESOURCE) {
- // Removal of "classes/" radical.
- path = entry.name();
- if (path.endsWith("module-info.class")) {
- path = "/" + path;
- } else {
- path = "/" + mn + "/" + path;
- }
- } else {
- // Entry.path() contains the kind of file native, conf, bin, ...
- // Keep it to avoid naming conflict (eg: native/jvm.cfg and config/jvm.cfg
- path = "/" + mn + "/" + entry.path();
- }
-
- resources.add(new ArchiveEntryResourcePoolEntry(mn, path, entry));
- }
+ entriesForModule.get(mn).stream()
+ .map(e -> new ArchiveEntryResourcePoolEntry(mn,
+ e.getResourcePoolEntryName(), e))
+ .forEach(resources::add);
}
return resources;
}
@@ -320,6 +304,20 @@
return result.toArray(array);
}
+ /**
+ * Returns the path of the resource.
+ */
+ public static String resourceName(String path) {
+ Objects.requireNonNull(path);
+ String s = path.substring(1);
+ int index = s.indexOf("/");
+ return s.substring(index + 1);
+ }
+
+ public static String toPackage(String name) {
+ return toPackage(name, false);
+ }
+
private static String toPackage(String name, boolean log) {
int index = name.lastIndexOf('/');
if (index > 0) {
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JarArchive.java Fri Sep 30 10:52:19 2016 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JarArchive.java Tue Oct 04 18:56:28 2016 -0700
@@ -43,7 +43,7 @@
/**
* An entry located in a jar file.
*/
- private class JarEntry extends Entry {
+ public class JarEntry extends Entry {
private final long size;
private final ZipEntry entry;
@@ -70,12 +70,10 @@
}
}
- private static final String MODULE_INFO = "module-info.class";
-
private final Path file;
private final String moduleName;
// currently processed ZipFile
- private ZipFile zipFile;
+ protected ZipFile zipFile;
protected JarArchive(String mn, Path file) {
Objects.requireNonNull(mn);
@@ -110,21 +108,7 @@
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);
- }
+ abstract Entry toEntry(ZipEntry ze);
@Override
public void close() throws IOException {
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JmodArchive.java Fri Sep 30 10:52:19 2016 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JmodArchive.java Tue Oct 04 18:56:28 2016 -0700
@@ -25,34 +25,106 @@
package jdk.tools.jlink.internal;
+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 jdk.internal.jmod.JmodFile;
import jdk.tools.jlink.internal.Archive.Entry.EntryType;
/**
* An Archive backed by a jmod file.
*/
-public class JmodArchive extends JarArchive {
-
+public class JmodArchive implements Archive {
private static final String JMOD_EXT = ".jmod";
- private static final String MODULE_NAME = "module";
- private static final String MODULE_INFO = "module-info.class";
- private static final String CLASSES = "classes";
- private static final String NATIVE_LIBS = "native";
- private static final String NATIVE_CMDS = "bin";
- private static final String CONFIG = "conf";
+
+ /**
+ * An entry located in a jmod file.
+ */
+ public class JmodEntry extends Entry {
+ private final JmodFile.Entry entry;
+
+ JmodEntry(String path, String name, EntryType type,
+ JmodFile.Entry entry) {
+ super(JmodArchive.this, path, name, type);
+ this.entry = Objects.requireNonNull(entry);
+ }
+
+ /**
+ * Returns the number of uncompressed bytes for this entry.
+ */
+ @Override
+ public long size() {
+ return entry.size();
+ }
+
+ @Override
+ public InputStream stream() throws IOException {
+ return jmodFile.getInputStream(entry.section(), entry.name());
+ }
+ }
+
+ private final Path file;
+ private final String moduleName;
+ private JmodFile jmodFile;
public JmodArchive(String mn, Path jmod) {
- super(mn, jmod);
- String filename = Objects.requireNonNull(jmod.getFileName()).toString();
+ Objects.requireNonNull(mn);
+ Objects.requireNonNull(jmod.getFileName());
+ String filename = jmod.toString();
if (!filename.endsWith(JMOD_EXT)) {
throw new UnsupportedOperationException("Unsupported format: " + filename);
}
+ this.moduleName = mn;
+ this.file = jmod;
+ }
+
+ @Override
+ public String moduleName() {
+ return moduleName;
+ }
+
+ @Override
+ public Path getPath() {
+ return file;
+ }
+
+ @Override
+ public Stream<Entry> entries() {
+ ensureOpen();
+ return jmodFile.stream()
+ .map(this::toEntry);
}
@Override
- EntryType toEntryType(String entryName) {
- String section = getSection(entryName.replace('\\', '/'));
+ public void open() throws IOException {
+ if (jmodFile != null) {
+ jmodFile.close();
+ }
+ this.jmodFile = new JmodFile(file);
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (jmodFile != null) {
+ jmodFile.close();
+ }
+ }
+
+ private void ensureOpen() {
+ if (jmodFile == null) {
+ try {
+ open();
+ } catch(IOException ioe){
+ throw new UncheckedIOException(ioe);
+ }
+ }
+ }
+
+ private EntryType toEntryType(JmodFile.Section section) {
switch (section) {
case CLASSES:
return EntryType.CLASS_OR_RESOURCE;
@@ -62,26 +134,23 @@
return EntryType.NATIVE_CMD;
case CONFIG:
return EntryType.CONFIG;
- case MODULE_NAME:
- return EntryType.MODULE_NAME;
default:
throw new InternalError("unexpected entry: " + section);
}
}
- private static String getSection(String entryName) {
- int i = entryName.indexOf('/');
- // Unnamed section.
- String section = "";
- if (i > 0) {
- section = entryName.substring(0, entryName.indexOf('/'));
+ private Entry toEntry(JmodFile.Entry entry) {
+ EntryType type = toEntryType(entry.section());
+ String name = entry.name();
+ String path = entry.section().jmodDir() + "/" + name;
+
+ // Entry.path() contains the kind of file native, conf, bin, ...
+ // Keep it to avoid naming conflict (eg: native/jvm.cfg and config/jvm.cfg
+ String resourceName = name;
+ if (type != EntryType.CLASS_OR_RESOURCE) {
+ resourceName = path;
}
- return section;
- }
- @Override
- String getFileName(String entryName) {
- entryName = entryName.replace('\\', '/');
- return entryName.substring(entryName.indexOf('/') + 1);
+ return new JmodEntry(path, resourceName, type, entry);
}
}
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ModularJarArchive.java Fri Sep 30 10:52:19 2016 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ModularJarArchive.java Tue Oct 04 18:56:28 2016 -0700
@@ -27,6 +27,8 @@
import java.nio.file.Path;
import java.util.Objects;
+import java.util.zip.ZipEntry;
+
import jdk.tools.jlink.internal.Archive.Entry.EntryType;
/**
@@ -35,6 +37,7 @@
public class ModularJarArchive extends JarArchive {
private static final String JAR_EXT = ".jar";
+ private static final String MODULE_INFO = "module-info.class";
public ModularJarArchive(String mn, Path jmod) {
super(mn, jmod);
@@ -50,6 +53,17 @@
}
@Override
+ Entry toEntry(ZipEntry ze) {
+ if (ze.isDirectory()) {
+ return null;
+ }
+
+ String name = ze.getName();
+ EntryType type = toEntryType(name);
+ return new JarEntry(ze.getName(), getFileName(name), type, zipFile, ze);
+ }
+
+ @Override
String getFileName(String entryName) {
return entryName;
}
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java Fri Sep 30 10:52:19 2016 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java Tue Oct 04 18:56:28 2016 -0700
@@ -27,15 +27,12 @@
import java.lang.module.ModuleDescriptor;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import java.util.function.Function;
import java.util.stream.Stream;
import jdk.internal.jimage.decompressor.CompressedResourceHeader;
import jdk.tools.jlink.plugin.ResourcePool;
@@ -44,7 +41,6 @@
import jdk.tools.jlink.plugin.ResourcePoolModule;
import jdk.tools.jlink.plugin.ResourcePoolModuleView;
import jdk.tools.jlink.plugin.PluginException;
-import jdk.tools.jlink.internal.plugins.FileCopierPlugin;
/**
* A manager for pool of resources.
@@ -100,17 +96,17 @@
@Override
public Set<String> packages() {
Set<String> pkgs = new HashSet<>();
- moduleContent.values().stream().filter(m -> m.type().
- equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)).forEach(res -> {
- // Module metadata only contains packages with .class files
- if (ImageFileCreator.isClassPackage(res.path())) {
- String[] split = ImageFileCreator.splitPath(res.path());
- String pkg = split[1];
- if (pkg != null && !pkg.isEmpty()) {
- pkgs.add(pkg);
+ moduleContent.values().stream()
+ .filter(m -> m.type() == ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
+ .forEach(res -> {
+ String name = ImageFileCreator.resourceName(res.path());
+ if (name.endsWith(".class") && !name.endsWith("module-info.class")) {
+ String pkg = ImageFileCreator.toPackage(name);
+ if (!pkg.isEmpty()) {
+ pkgs.add(pkg);
+ }
}
- }
- });
+ });
return pkgs;
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodOutputStream.java Tue Oct 04 18:56:28 2016 -0700
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.tools.jmod;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import static jdk.internal.jmod.JmodFile.*;
+
+/**
+ * Output stream to write to JMOD file
+ */
+class JmodOutputStream extends OutputStream implements AutoCloseable {
+ /**
+ * This method creates (or overrides, if exists) the JMOD file,
+ * returning the the output stream to write to the JMOD file.
+ */
+ static JmodOutputStream newOutputStream(Path file) throws IOException {
+ OutputStream out = Files.newOutputStream(file);
+ BufferedOutputStream bos = new BufferedOutputStream(out);
+ return new JmodOutputStream(bos);
+ }
+
+ private final ZipOutputStream zos;
+ private JmodOutputStream(OutputStream out) {
+ this.zos = new ZipOutputStream(out);
+ try {
+ out.write(JMOD_MAGIC_NUMBER);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ /**
+ * Writes the input stream to the named entry of the given section.
+ */
+ public void writeEntry(InputStream in, Section section, String name)
+ throws IOException
+ {
+ ZipEntry ze = newEntry(section, name);
+ zos.putNextEntry(ze);
+ in.transferTo(zos);
+ zos.closeEntry();
+ }
+
+ /**
+ * Writes the given bytes to the named entry of the given section.
+ */
+ public void writeEntry(byte[] bytes, Section section, String path)
+ throws IOException
+ {
+ ZipEntry ze = newEntry(section, path);
+ zos.putNextEntry(ze);
+ zos.write(bytes);
+ zos.closeEntry();
+ }
+
+ /**
+ * Writes the given entry to the given input stream.
+ */
+ public void writeEntry(InputStream in, Entry e) throws IOException {
+ zos.putNextEntry(e.zipEntry());
+ zos.write(in.readAllBytes());
+ zos.closeEntry();
+ }
+
+ private ZipEntry newEntry(Section section, String path) {
+ String prefix = section.jmodDir();
+ String name = Paths.get(prefix, path).toString()
+ .replace(File.separatorChar, '/');
+ return new ZipEntry(name);
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ zos.write(b);
+ }
+
+ @Override
+ public void close() throws IOException {
+ zos.close();
+ }
+}
+
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java Fri Sep 30 10:52:19 2016 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java Tue Oct 04 18:56:28 2016 -0700
@@ -25,8 +25,6 @@
package jdk.tools.jmod;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -60,7 +58,6 @@
import java.text.MessageFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -80,15 +77,16 @@
import java.util.function.Supplier;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
import java.util.stream.Collectors;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
+import jdk.internal.jmod.JmodFile;
+import jdk.internal.jmod.JmodFile.Section;
import jdk.internal.joptsimple.BuiltinHelpFormatter;
import jdk.internal.joptsimple.NonOptionArgumentSpec;
import jdk.internal.joptsimple.OptionDescriptor;
@@ -250,23 +248,14 @@
}
private boolean describe() throws IOException {
- ZipFile zip = null;
- try {
- try {
- zip = new ZipFile(options.jmodFile.toFile());
- } catch (IOException x) {
- throw new IOException("error opening jmod file", x);
+ try (JmodFile jf = new JmodFile(options.jmodFile)) {
+ try (InputStream in = jf.getInputStream(Section.CLASSES, MODULE_INFO)) {
+ ModuleDescriptor md = ModuleDescriptor.read(in);
+ printModuleDescriptor(md);
+ return true;
+ } catch (IOException e) {
+ throw new CommandException("err.module.descriptor.not.found");
}
-
- try (InputStream in = Files.newInputStream(options.jmodFile)) {
- boolean found = printModuleDescriptor(in);
- if (!found)
- throw new CommandException("err.module.descriptor.not.found");
- return found;
- }
- } finally {
- if (zip != null)
- zip.close();
}
}
@@ -278,65 +267,52 @@
private static final JavaLangModuleAccess JLMA = SharedSecrets.getJavaLangModuleAccess();
- private boolean printModuleDescriptor(InputStream in)
+ private void printModuleDescriptor(ModuleDescriptor md)
throws IOException
{
- final String mi = Section.CLASSES.jmodDir() + "/" + MODULE_INFO;
- try (BufferedInputStream bis = new BufferedInputStream(in);
- ZipInputStream zis = new ZipInputStream(bis)) {
-
- ZipEntry e;
- while ((e = zis.getNextEntry()) != null) {
- if (e.getName().equals(mi)) {
- ModuleDescriptor md = ModuleDescriptor.read(zis);
- StringBuilder sb = new StringBuilder();
- sb.append("\n").append(md.toNameAndVersion());
+ StringBuilder sb = new StringBuilder();
+ sb.append("\n").append(md.toNameAndVersion());
- md.requires().stream()
- .sorted(Comparator.comparing(Requires::name))
- .forEach(r -> {
- sb.append("\n requires ");
- if (!r.modifiers().isEmpty())
- sb.append(toString(r.modifiers())).append(" ");
- sb.append(r.name());
- });
+ md.requires().stream()
+ .sorted(Comparator.comparing(Requires::name))
+ .forEach(r -> {
+ sb.append("\n requires ");
+ if (!r.modifiers().isEmpty())
+ sb.append(toString(r.modifiers())).append(" ");
+ sb.append(r.name());
+ });
- md.uses().stream().sorted()
- .forEach(s -> sb.append("\n uses ").append(s));
+ md.uses().stream().sorted()
+ .forEach(s -> sb.append("\n uses ").append(s));
- md.exports().stream()
- .sorted(Comparator.comparing(Exports::source))
- .forEach(p -> sb.append("\n exports ").append(p));
+ md.exports().stream()
+ .sorted(Comparator.comparing(Exports::source))
+ .forEach(p -> sb.append("\n exports ").append(p));
+
+ md.conceals().stream().sorted()
+ .forEach(p -> sb.append("\n conceals ").append(p));
- md.conceals().stream().sorted()
- .forEach(p -> sb.append("\n conceals ").append(p));
+ md.provides().values().stream()
+ .sorted(Comparator.comparing(Provides::service))
+ .forEach(p -> sb.append("\n provides ").append(p.service())
+ .append(" with ")
+ .append(toString(p.providers())));
- md.provides().values().stream()
- .sorted(Comparator.comparing(Provides::service))
- .forEach(p -> sb.append("\n provides ").append(p.service())
- .append(" with ")
- .append(toString(p.providers())));
+ md.mainClass().ifPresent(v -> sb.append("\n main-class " + v));
- md.mainClass().ifPresent(v -> sb.append("\n main-class " + v));
-
- md.osName().ifPresent(v -> sb.append("\n operating-system-name " + v));
-
- md.osArch().ifPresent(v -> sb.append("\n operating-system-architecture " + v));
+ md.osName().ifPresent(v -> sb.append("\n operating-system-name " + v));
- md.osVersion().ifPresent(v -> sb.append("\n operating-system-version " + v));
+ md.osArch().ifPresent(v -> sb.append("\n operating-system-architecture " + v));
+
+ md.osVersion().ifPresent(v -> sb.append("\n operating-system-version " + v));
- JLMA.hashes(md).ifPresent(
- hashes -> hashes.names().stream().sorted().forEach(
- mod -> sb.append("\n hashes ").append(mod).append(" ")
- .append(hashes.algorithm()).append(" ")
- .append(hashes.hashFor(mod))));
+ JLMA.hashes(md).ifPresent(
+ hashes -> hashes.names().stream().sorted().forEach(
+ mod -> sb.append("\n hashes ").append(mod).append(" ")
+ .append(hashes.algorithm()).append(" ")
+ .append(hashes.hashFor(mod))));
- out.println(sb.toString());
- return true;
- }
- }
- }
- return false;
+ out.println(sb.toString());
}
private boolean create() throws IOException {
@@ -347,9 +323,8 @@
Path target = options.jmodFile;
Path tempTarget = target.resolveSibling(target.getFileName() + ".tmp");
try {
- try (OutputStream out = Files.newOutputStream(tempTarget);
- BufferedOutputStream bos = new BufferedOutputStream(out)) {
- jmod.write(bos);
+ try (JmodOutputStream jos = JmodOutputStream.newOutputStream(tempTarget)) {
+ jmod.write(jos);
}
Files.move(tempTarget, target);
} catch (Exception e) {
@@ -383,19 +358,16 @@
/**
* Writes the jmod to the given output stream.
*/
- void write(OutputStream out) throws IOException {
- try (ZipOutputStream zos = new ZipOutputStream(out)) {
-
- // module-info.class
- writeModuleInfo(zos, findPackages(classpath));
+ void write(JmodOutputStream out) throws IOException {
+ // module-info.class
+ writeModuleInfo(out, findPackages(classpath));
- // classes
- processClasses(zos, classpath);
+ // classes
+ processClasses(out, classpath);
- processSection(zos, Section.NATIVE_CMDS, cmds);
- processSection(zos, Section.NATIVE_LIBS, libs);
- processSection(zos, Section.CONFIG, configs);
- }
+ processSection(out, Section.NATIVE_CMDS, cmds);
+ processSection(out, Section.NATIVE_LIBS, libs);
+ processSection(out, Section.CONFIG, configs);
}
/**
@@ -441,7 +413,7 @@
* then the corresponding class file attributes are added to the
* module-info here.
*/
- void writeModuleInfo(ZipOutputStream zos, Set<String> packages)
+ void writeModuleInfo(JmodOutputStream out, Set<String> packages)
throws IOException
{
Supplier<InputStream> miSupplier = newModuleInfoSupplier();
@@ -492,11 +464,7 @@
}
// write the (possibly extended or modified) module-info.class
- String e = Section.CLASSES.jmodDir() + "/" + MODULE_INFO;
- ZipEntry ze = new ZipEntry(e);
- zos.putNextEntry(ze);
- extender.write(zos);
- zos.closeEntry();
+ out.writeEntry(extender.toByteArray(), Section.CLASSES, MODULE_INFO);
}
}
@@ -627,7 +595,7 @@
return "";
}
- void processClasses(ZipOutputStream zos, List<Path> classpaths)
+ void processClasses(JmodOutputStream zos, List<Path> classpaths)
throws IOException
{
if (classpaths == null)
@@ -645,7 +613,7 @@
}
}
- void processSection(ZipOutputStream zos, Section section, List<Path> paths)
+ void processSection(JmodOutputStream zos, Section section, List<Path> paths)
throws IOException
{
if (paths == null)
@@ -655,11 +623,9 @@
processSection(zos, section, p);
}
- void processSection(ZipOutputStream zos, Section section, Path top)
+ void processSection(JmodOutputStream out, Section section, Path top)
throws IOException
{
- final String prefix = section.jmodDir();
-
Files.walkFileTree(top, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
@@ -667,13 +633,19 @@
{
Path relPath = top.relativize(file);
if (relPath.toString().equals(MODULE_INFO)
- && !Section.CLASSES.equals(section))
+ && !Section.CLASSES.equals(section))
warning("warn.ignore.entry", MODULE_INFO, section);
if (!relPath.toString().equals(MODULE_INFO)
- && !matches(relPath, excludes)) {
+ && !matches(relPath, excludes)) {
try (InputStream in = Files.newInputStream(file)) {
- writeZipEntry(zos, in, prefix, relPath.toString());
+ out.writeEntry(in, section, relPath.toString());
+ } catch (IOException x) {
+ if (x.getMessage().contains("duplicate entry")) {
+ warning("warn.ignore.duplicate.entry", relPath.toString(), section);
+ return FileVisitResult.CONTINUE;
+ }
+ throw x;
}
}
return FileVisitResult.CONTINUE;
@@ -691,36 +663,17 @@
return false;
}
- void writeZipEntry(ZipOutputStream zos, InputStream in, String prefix, String other)
- throws IOException
- {
- String name = Paths.get(prefix, other).toString()
- .replace(File.separatorChar, '/');
- ZipEntry ze = new ZipEntry(name);
- try {
- zos.putNextEntry(ze);
- in.transferTo(zos);
- zos.closeEntry();
- } catch (ZipException x) {
- if (x.getMessage().contains("duplicate entry")) {
- warning("warn.ignore.duplicate.entry", name, prefix);
- return;
- }
- throw x;
- }
- }
-
class JarEntryConsumer implements Consumer<JarEntry>, Predicate<JarEntry> {
- final ZipOutputStream zos;
+ final JmodOutputStream out;
final JarFile jarfile;
- JarEntryConsumer(ZipOutputStream zos, JarFile jarfile) {
- this.zos = zos;
+ JarEntryConsumer(JmodOutputStream out, JarFile jarfile) {
+ this.out = out;
this.jarfile = jarfile;
}
@Override
public void accept(JarEntry je) {
try (InputStream in = jarfile.getInputStream(je)) {
- writeZipEntry(zos, in, Section.CLASSES.jmodDir(), je.getName());
+ out.writeEntry(in, Section.CLASSES, je.getName());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
@@ -947,29 +900,11 @@
{
Path target = moduleNameToPath.get(name);
Path tempTarget = target.resolveSibling(target.getFileName() + ".tmp");
- ZipFile zip = new ZipFile(target.toFile());
try {
- try (OutputStream out = Files.newOutputStream(tempTarget);
- ZipOutputStream zos = new ZipOutputStream(out)) {
- zip.stream().forEach(e -> {
- try {
- InputStream in = zip.getInputStream(e);
- if (e.getName().equals(MODULE_INFO) ||
- e.getName().equals(Section.CLASSES.jmodDir() + "/" + MODULE_INFO)) {
- ZipEntry ze = new ZipEntry(e.getName());
- ze.setTime(System.currentTimeMillis());
- zos.putNextEntry(ze);
- recordHashes(in, zos, moduleHashes);
- zos.closeEntry();
- } else {
- zos.putNextEntry(e);
- zos.write(in.readAllBytes());
- zos.closeEntry();
- }
- } catch (IOException x) {
- throw new UncheckedIOException(x);
- }
- });
+ if (target.getFileName().toString().endsWith(".jmod")) {
+ updateJmodFile(target, tempTarget, moduleHashes);
+ } else {
+ updateModularJar(target, tempTarget, moduleHashes);
}
} catch (IOException|RuntimeException e) {
if (Files.exists(tempTarget)) {
@@ -980,13 +915,67 @@
}
}
throw e;
- } finally {
- zip.close();
}
+
out.println(getMessage("module.hashes.recorded", name));
Files.move(tempTarget, target, StandardCopyOption.REPLACE_EXISTING);
}
+ private void updateModularJar(Path target, Path tempTarget,
+ ModuleHashes moduleHashes)
+ throws IOException
+ {
+ try (JarFile jf = new JarFile(target.toFile());
+ OutputStream out = Files.newOutputStream(tempTarget);
+ JarOutputStream jos = new JarOutputStream(out))
+ {
+ jf.stream().forEach(e -> {
+ try (InputStream in = jf.getInputStream(e)) {
+ if (e.getName().equals(MODULE_INFO)) {
+ // what about module-info.class in versioned entries?
+ ZipEntry ze = new ZipEntry(e.getName());
+ ze.setTime(System.currentTimeMillis());
+ jos.putNextEntry(ze);
+ recordHashes(in, jos, moduleHashes);
+ jos.closeEntry();
+ } else {
+ jos.putNextEntry(e);
+ jos.write(in.readAllBytes());
+ jos.closeEntry();
+ }
+ } catch (IOException x) {
+ throw new UncheckedIOException(x);
+ }
+ });
+ }
+ }
+
+ private void updateJmodFile(Path target, Path tempTarget,
+ ModuleHashes moduleHashes)
+ throws IOException
+ {
+
+ try (JmodFile jf = new JmodFile(target);
+ JmodOutputStream jos = JmodOutputStream.newOutputStream(tempTarget))
+ {
+ jf.stream().forEach(e -> {
+ try (InputStream in = jf.getInputStream(e.section(), e.name())) {
+ if (e.name().equals(MODULE_INFO)) {
+ // replace module-info.class
+ ModuleInfoExtender extender =
+ ModuleInfoExtender.newExtender(in);
+ extender.hashes(moduleHashes);
+ jos.writeEntry(extender.toByteArray(), e.section(), e.name());
+ } else {
+ jos.writeEntry(in, e);
+ }
+ } catch (IOException x) {
+ throw new UncheckedIOException(x);
+ }
+ });
+ }
+ }
+
private Path moduleToPath(String name) {
ModuleReference mref = moduleFinder.find(name).orElseThrow(
() -> new InternalError("Selected module " + name + " not on module path"));
@@ -1001,22 +990,6 @@
}
}
- enum Section {
- NATIVE_LIBS("native"),
- NATIVE_CMDS("bin"),
- CLASSES("classes"),
- CONFIG("conf"),
- UNKNOWN("unknown");
-
- private final String jmodDir;
-
- Section(String jmodDir) {
- this.jmodDir = jmodDir;
- }
-
- String jmodDir() { return jmodDir; }
- }
-
static class ClassPathConverter implements ValueConverter<Path> {
static final ValueConverter<Path> INSTANCE = new ClassPathConverter();
--- a/jdk/test/tools/jlink/JLinkNegativeTest.java Fri Sep 30 10:52:19 2016 -0700
+++ b/jdk/test/tools/jlink/JLinkNegativeTest.java Tue Oct 04 18:56:28 2016 -0700
@@ -191,7 +191,7 @@
.output(imageFile)
.addMods("not_zip")
.modulePath(helper.defaultModulePath())
- .call().assertFailure("Error: java.util.zip.ZipException: zip file is empty");
+ .call().assertFailure("Error: java.io.IOException: Invalid jmod file");
} finally {
deleteDirectory(jmod);
}
@@ -236,13 +236,10 @@
JImageGenerator.addFiles(module, new InMemoryFile("unknown/A.class", new byte[0]));
try {
Result result = helper.generateDefaultImage(moduleName);
- if (result.getExitCode() != 4) {
+ System.err.println(result.getMessage());
+ if (result.getExitCode() == 0) {
throw new AssertionError("Crash expected");
}
- if (!result.getMessage().contains("java.lang.InternalError: unexpected entry: unknown")) {
- System.err.println(result.getMessage());
- throw new AssertionError("InternalError expected");
- }
} finally {
deleteDirectory(module);
}
@@ -250,7 +247,7 @@
@Test(enabled = true)
public void testSectionsAreFiles() throws IOException {
- String moduleName = "module";
+ String moduleName = "hacked4";
Path jmod = helper.generateDefaultJModule(moduleName).assertSuccess();
JImageGenerator.addFiles(jmod,
new InMemoryFile("/native", new byte[0]),
@@ -258,13 +255,10 @@
new InMemoryFile("/bin", new byte[0]));
try {
Result result = helper.generateDefaultImage(moduleName);
- if (result.getExitCode() != 4) {
+ System.err.println(result.getMessage());
+ if (result.getExitCode() == 0) {
throw new AssertionError("Crash expected");
}
- if (!result.getMessage().contains("java.lang.InternalError: unexpected entry: ")) {
- System.err.println(result.getMessage());
- throw new AssertionError("InternalError expected");
- }
} finally {
deleteDirectory(jmod);
}
--- a/jdk/test/tools/jlink/JLinkTest.java Fri Sep 30 10:52:19 2016 -0700
+++ b/jdk/test/tools/jlink/JLinkTest.java Tue Oct 04 18:56:28 2016 -0700
@@ -122,15 +122,6 @@
}
{
- String moduleName = "filter";
- Path jmod = helper.generateDefaultJModule(moduleName).assertSuccess();
- String className = "_A.class";
- JImageGenerator.addFiles(jmod, new InMemoryFile(className, new byte[0]));
- Path image = helper.generateDefaultImage(moduleName).assertSuccess();
- helper.checkImage(image, moduleName, new String[] {"/" + moduleName + "/" + className}, null);
- }
-
- {
String moduleName = "m"; // 8163382
Path jmod = helper.generateDefaultJModule(moduleName).assertSuccess();
JImageGenerator.getJLinkTask()