From 6504983459c6a85b328efd9a245d1a5eb8d50771 Mon Sep 17 00:00:00 2001 From: Mandy Chung Date: Tue, 4 Oct 2016 18:56:28 -0700 Subject: [PATCH] 8166860: Add magic number to jmod file Reviewed-by: alanb, jjg --- .../classes/java/lang/module/ModulePath.java | 22 +- .../classes/jdk/internal/jmod/JmodFile.java | 206 +++++++++++++ .../internal/module/ModuleInfoExtender.java | 13 +- .../java.base/share/classes/module-info.java | 3 + .../jdk/tools/jlink/internal/Archive.java | 27 +- .../jdk/tools/jlink/internal/DirArchive.java | 8 +- .../jlink/internal/ImageFileCreator.java | 42 ++- .../jdk/tools/jlink/internal/JarArchive.java | 22 +- .../jdk/tools/jlink/internal/JmodArchive.java | 129 ++++++-- .../jlink/internal/ModularJarArchive.java | 14 + .../jlink/internal/ResourcePoolManager.java | 24 +- .../jdk/tools/jmod/JmodOutputStream.java | 116 +++++++ .../classes/jdk/tools/jmod/JmodTask.java | 289 ++++++++---------- jdk/test/tools/jlink/JLinkNegativeTest.java | 18 +- jdk/test/tools/jlink/JLinkTest.java | 9 - 15 files changed, 649 insertions(+), 293 deletions(-) create mode 100644 jdk/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java create mode 100644 jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodOutputStream.java diff --git a/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java b/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java index d300bc007e3..cad2812ab64 100644 --- a/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java +++ b/jdk/src/java.base/share/classes/java/lang/module/ModulePath.java @@ -56,6 +56,8 @@ import java.util.stream.Stream; 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 @@ class ModulePath implements ConfigurableModuleFinder { // -- jmod files -- - private Set 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 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 @@ class ModulePath implements ConfigurableModuleFinder { * @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); } diff --git a/jdk/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java b/jdk/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java new file mode 100644 index 00000000000..1d6ccbaf6c2 --- /dev/null +++ b/jdk/src/java.base/share/classes/jdk/internal/jmod/JmodFile.java @@ -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 stream() { + return zipfile.stream() + .filter(e -> !e.isDirectory()) + .map(Entry::new); + } + + @Override + public void close() throws IOException { + if (zipfile != null) { + zipfile.close(); + } + } +} diff --git a/jdk/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java b/jdk/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java index f3314aa73e5..3c44964ba12 100644 --- a/jdk/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java +++ b/jdk/src/java.base/share/classes/jdk/internal/module/ModuleInfoExtender.java @@ -163,6 +163,16 @@ public final class ModuleInfoExtender { * 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 @@ public final class ModuleInfoExtender { // add any attributes that didn't replace previous attributes cv.finish(); - // emit to the output stream - out.write(cw.toByteArray()); + return cw.toByteArray(); } /** diff --git a/jdk/src/java.base/share/classes/module-info.java b/jdk/src/java.base/share/classes/module-info.java index 11d53463dc8..aef7c1cdf1e 100644 --- a/jdk/src/java.base/share/classes/module-info.java +++ b/jdk/src/java.base/share/classes/module-info.java @@ -124,6 +124,9 @@ module java.base { 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 diff --git a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java index 5cf159104e0..e2c4c49ad66 100644 --- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java +++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java @@ -55,6 +55,13 @@ public interface Archive { 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 @@ public interface Archive { 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; diff --git a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/DirArchive.java b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/DirArchive.java index 326417c925d..7b8c2c88655 100644 --- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/DirArchive.java +++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/DirArchive.java @@ -50,7 +50,7 @@ public class DirArchive implements Archive { 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 @@ public class DirArchive implements Archive { 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); } diff --git a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java index c70d6761c7a..93625f71b82 100644 --- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java +++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java @@ -40,6 +40,7 @@ import java.util.Objects; 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 final class ImageFileCreator { }); } - public static boolean isClassPackage(String path) { - return path.endsWith(".class") && !path.endsWith("module-info.class"); - } - public static void recreateJimage(Path jimageFile, Set archives, ImagePluginStack pluginSupport) @@ -265,26 +262,13 @@ public final class ImageFileCreator { 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 @@ public final class ImageFileCreator { 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) { diff --git a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JarArchive.java b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JarArchive.java index c7c70dad584..0e2eb4b5342 100644 --- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JarArchive.java +++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JarArchive.java @@ -43,7 +43,7 @@ public abstract class JarArchive implements Archive { /** * 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 @@ public abstract class JarArchive implements Archive { } } - 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 @@ public abstract class JarArchive implements Archive { 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 { diff --git a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JmodArchive.java b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JmodArchive.java index 5997ea03379..c70cb104d5b 100644 --- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JmodArchive.java +++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JmodArchive.java @@ -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"; - public JmodArchive(String mn, Path jmod) { - super(mn, jmod); - String filename = Objects.requireNonNull(jmod.getFileName()).toString(); - if (!filename.endsWith(JMOD_EXT)) { - throw new UnsupportedOperationException("Unsupported format: " + filename); + /** + * 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) { + 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 - EntryType toEntryType(String entryName) { - String section = getSection(entryName.replace('\\', '/')); + public String moduleName() { + return moduleName; + } + + @Override + public Path getPath() { + return file; + } + + @Override + public Stream entries() { + ensureOpen(); + return jmodFile.stream() + .map(this::toEntry); + } + + @Override + 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 @@ public class JmodArchive extends JarArchive { 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('/')); - } - return section; - } + private Entry toEntry(JmodFile.Entry entry) { + EntryType type = toEntryType(entry.section()); + String name = entry.name(); + String path = entry.section().jmodDir() + "/" + name; - @Override - String getFileName(String entryName) { - entryName = entryName.replace('\\', '/'); - return entryName.substring(entryName.indexOf('/') + 1); + // 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 new JmodEntry(path, resourceName, type, entry); } } diff --git a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ModularJarArchive.java b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ModularJarArchive.java index 0c005fe52e8..909fb141d1a 100644 --- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ModularJarArchive.java +++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ModularJarArchive.java @@ -27,6 +27,8 @@ package jdk.tools.jlink.internal; 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 @@ import jdk.tools.jlink.internal.Archive.Entry.EntryType; 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); @@ -49,6 +52,17 @@ public class ModularJarArchive extends JarArchive { return EntryType.CLASS_OR_RESOURCE; } + @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; diff --git a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java index 47447c99ec2..6647df0dff2 100644 --- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java +++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java @@ -27,15 +27,12 @@ package jdk.tools.jlink.internal; 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.ResourcePoolEntry; 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 @@ public class ResourcePoolManager { @Override public Set packages() { Set 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; } diff --git a/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodOutputStream.java b/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodOutputStream.java new file mode 100644 index 00000000000..c56e53c0593 --- /dev/null +++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodOutputStream.java @@ -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(); + } +} + diff --git a/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java b/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java index a799ce3ebd3..6c23ce01bba 100644 --- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java +++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jmod/JmodTask.java @@ -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.nio.file.attribute.BasicFileAttributes; 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.Predicate; 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 @@ public class JmodTask { } 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 @@ public class JmodTask { 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)) { + StringBuilder sb = new StringBuilder(); + sb.append("\n").append(md.toNameAndVersion()); - 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()); + 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.osName().ifPresent(v -> sb.append("\n operating-system-name " + v)); + md.osArch().ifPresent(v -> sb.append("\n operating-system-architecture " + v)); - md.osArch().ifPresent(v -> sb.append("\n operating-system-architecture " + v)); + md.osVersion().ifPresent(v -> sb.append("\n operating-system-version " + 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 @@ public class JmodTask { 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 @@ public class JmodTask { /** * Writes the jmod to the given output stream. */ - void write(OutputStream out) throws IOException { - try (ZipOutputStream zos = new ZipOutputStream(out)) { + void write(JmodOutputStream out) throws IOException { + // module-info.class + writeModuleInfo(out, findPackages(classpath)); - // module-info.class - writeModuleInfo(zos, findPackages(classpath)); + // classes + processClasses(out, classpath); - // classes - processClasses(zos, 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 @@ public class JmodTask { * then the corresponding class file attributes are added to the * module-info here. */ - void writeModuleInfo(ZipOutputStream zos, Set packages) + void writeModuleInfo(JmodOutputStream out, Set packages) throws IOException { Supplier miSupplier = newModuleInfoSupplier(); @@ -492,11 +464,7 @@ public class JmodTask { } // 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 @@ public class JmodTask { return ""; } - void processClasses(ZipOutputStream zos, List classpaths) + void processClasses(JmodOutputStream zos, List classpaths) throws IOException { if (classpaths == null) @@ -645,7 +613,7 @@ public class JmodTask { } } - void processSection(ZipOutputStream zos, Section section, List paths) + void processSection(JmodOutputStream zos, Section section, List paths) throws IOException { if (paths == null) @@ -655,11 +623,9 @@ public class JmodTask { 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() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) @@ -667,13 +633,19 @@ public class JmodTask { { 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 @@ public class JmodTask { 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, Predicate { - 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 @@ public class JmodTask { { 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 @@ public class JmodTask { } } 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 @@ public class JmodTask { } } - 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 { static final ValueConverter INSTANCE = new ClassPathConverter(); diff --git a/jdk/test/tools/jlink/JLinkNegativeTest.java b/jdk/test/tools/jlink/JLinkNegativeTest.java index 9213626dfeb..95ed27b5510 100644 --- a/jdk/test/tools/jlink/JLinkNegativeTest.java +++ b/jdk/test/tools/jlink/JLinkNegativeTest.java @@ -191,7 +191,7 @@ public class JLinkNegativeTest { .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 @@ public class JLinkNegativeTest { 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 @@ public class JLinkNegativeTest { @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 @@ public class JLinkNegativeTest { 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); } diff --git a/jdk/test/tools/jlink/JLinkTest.java b/jdk/test/tools/jlink/JLinkTest.java index 6dda563645b..d6e9d1bca92 100644 --- a/jdk/test/tools/jlink/JLinkTest.java +++ b/jdk/test/tools/jlink/JLinkTest.java @@ -121,15 +121,6 @@ public class JLinkTest { .call().assertFailure("Error: no value given for --module-path"); } - { - 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();