8166860: Add magic number to jmod file

Reviewed-by: alanb, jjg
This commit is contained in:
Mandy Chung 2016-10-04 18:56:28 -07:00
parent e4d6418631
commit 6504983459
15 changed files with 649 additions and 293 deletions

View File

@ -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<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 @@ 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);
}

View File

@ -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();
}
}
}

View File

@ -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();
}
/**

View File

@ -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

View File

@ -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;

View File

@ -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);
}

View File

@ -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<Archive> 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) {

View File

@ -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 {

View File

@ -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<Entry> 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);
}
}

View File

@ -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;

View File

@ -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<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;
}

View File

@ -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();
}
}

View File

@ -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<String> packages)
void writeModuleInfo(JmodOutputStream out, Set<String> packages)
throws IOException
{
Supplier<InputStream> 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<Path> classpaths)
void processClasses(JmodOutputStream zos, List<Path> classpaths)
throws IOException
{
if (classpaths == null)
@ -645,7 +613,7 @@ public class JmodTask {
}
}
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 @@ 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<Path>() {
@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<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 @@ 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<Path> {
static final ValueConverter<Path> INSTANCE = new ClassPathConverter();

View File

@ -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);
}

View File

@ -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();