blob: 93625f71b820956386235c1bde3c3eea2d8d42d9 [file] [log] [blame]
/*
* Copyright (c) 2014, 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.jlink.internal;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolEntry;
/**
* An image (native endian.)
* <pre>{@code
* {
* u4 magic;
* u2 major_version;
* u2 minor_version;
* u4 resource_count;
* u4 table_length;
* u4 location_attributes_size;
* u4 strings_size;
* u4 redirect[table_length];
* u4 offsets[table_length];
* u1 location_attributes[location_attributes_size];
* u1 strings[strings_size];
* u1 content[if !EOF];
* }
* }</pre>
*/
public final class ImageFileCreator {
private final Map<String, List<Entry>> entriesForModule = new HashMap<>();
private final ImagePluginStack plugins;
private ImageFileCreator(ImagePluginStack plugins) {
this.plugins = Objects.requireNonNull(plugins);
}
public static ExecutableImage create(Set<Archive> archives,
ImagePluginStack plugins)
throws IOException {
return ImageFileCreator.create(archives, ByteOrder.nativeOrder(),
plugins);
}
public static ExecutableImage create(Set<Archive> archives,
ByteOrder byteOrder)
throws IOException {
return ImageFileCreator.create(archives, byteOrder,
new ImagePluginStack());
}
public static ExecutableImage create(Set<Archive> archives,
ByteOrder byteOrder,
ImagePluginStack plugins)
throws IOException
{
ImageFileCreator image = new ImageFileCreator(plugins);
try {
image.readAllEntries(archives);
// write to modular image
image.writeImage(archives, byteOrder);
} finally {
//Close all archives
for (Archive a : archives) {
a.close();
}
}
return plugins.getExecutableImage();
}
private void readAllEntries(Set<Archive> archives) {
archives.stream().forEach((archive) -> {
Map<Boolean, List<Entry>> es;
try (Stream<Entry> entries = archive.entries()) {
es = entries.collect(Collectors.partitioningBy(n -> n.type()
== EntryType.CLASS_OR_RESOURCE));
}
String mn = archive.moduleName();
List<Entry> all = new ArrayList<>();
all.addAll(es.get(false));
all.addAll(es.get(true));
entriesForModule.put(mn, all);
});
}
public static void recreateJimage(Path jimageFile,
Set<Archive> archives,
ImagePluginStack pluginSupport)
throws IOException {
try {
Map<String, List<Entry>> entriesForModule
= archives.stream().collect(Collectors.toMap(
Archive::moduleName,
a -> {
try (Stream<Entry> entries = a.entries()) {
return entries.collect(Collectors.toList());
}
}));
ByteOrder order = ByteOrder.nativeOrder();
BasicImageWriter writer = new BasicImageWriter(order);
ResourcePoolManager pool = createPoolManager(archives, entriesForModule, order, writer);
try (OutputStream fos = Files.newOutputStream(jimageFile);
BufferedOutputStream bos = new BufferedOutputStream(fos);
DataOutputStream out = new DataOutputStream(bos)) {
generateJImage(pool, writer, pluginSupport, out);
}
} finally {
//Close all archives
for (Archive a : archives) {
a.close();
}
}
}
private void writeImage(Set<Archive> archives,
ByteOrder byteOrder)
throws IOException {
BasicImageWriter writer = new BasicImageWriter(byteOrder);
ResourcePoolManager allContent = createPoolManager(archives,
entriesForModule, byteOrder, writer);
ResourcePool result = generateJImage(allContent,
writer, plugins, plugins.getJImageFileOutputStream());
//Handle files.
try {
plugins.storeFiles(allContent.resourcePool(), result, writer);
} catch (Exception ex) {
if (JlinkTask.DEBUG) {
ex.printStackTrace();
}
throw new IOException(ex);
}
}
private static ResourcePool generateJImage(ResourcePoolManager allContent,
BasicImageWriter writer,
ImagePluginStack pluginSupport,
DataOutputStream out
) throws IOException {
ResourcePool resultResources;
try {
resultResources = pluginSupport.visitResources(allContent);
} catch (PluginException pe) {
if (JlinkTask.DEBUG) {
pe.printStackTrace();
}
throw pe;
} catch (Exception ex) {
if (JlinkTask.DEBUG) {
ex.printStackTrace();
}
throw new IOException(ex);
}
Set<String> duplicates = new HashSet<>();
long[] offset = new long[1];
List<ResourcePoolEntry> content = new ArrayList<>();
List<String> paths = new ArrayList<>();
// the order of traversing the resources and the order of
// the module content being written must be the same
resultResources.entries().forEach(res -> {
if (res.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)) {
String path = res.path();
content.add(res);
long uncompressedSize = res.contentLength();
long compressedSize = 0;
if (res instanceof CompressedModuleData) {
CompressedModuleData comp
= (CompressedModuleData) res;
compressedSize = res.contentLength();
uncompressedSize = comp.getUncompressedSize();
}
long onFileSize = res.contentLength();
if (duplicates.contains(path)) {
System.err.format("duplicate resource \"%s\", skipping%n",
path);
// TODO Need to hang bytes on resource and write
// from resource not zip.
// Skipping resource throws off writing from zip.
offset[0] += onFileSize;
return;
}
duplicates.add(path);
writer.addLocation(path, offset[0], compressedSize, uncompressedSize);
paths.add(path);
offset[0] += onFileSize;
}
});
ImageResourcesTree tree = new ImageResourcesTree(offset[0], writer, paths);
// write header and indices
byte[] bytes = writer.getBytes();
out.write(bytes, 0, bytes.length);
// write module content
content.stream().forEach((res) -> {
res.write(out);
});
tree.addContent(out);
out.close();
return resultResources;
}
private static ResourcePoolManager createPoolManager(Set<Archive> archives,
Map<String, List<Entry>> entriesForModule,
ByteOrder byteOrder,
BasicImageWriter writer) throws IOException {
ResourcePoolManager resources = new ResourcePoolManager(byteOrder, new StringTable() {
@Override
public int addString(String str) {
return writer.addString(str);
}
@Override
public String getString(int id) {
return writer.getString(id);
}
});
for (Archive archive : archives) {
String mn = archive.moduleName();
entriesForModule.get(mn).stream()
.map(e -> new ArchiveEntryResourcePoolEntry(mn,
e.getResourcePoolEntryName(), e))
.forEach(resources::add);
}
return resources;
}
/**
* Helper method that splits a Resource path onto 3 items: module, parent
* and resource name.
*
* @param path
* @return An array containing module, parent and name.
*/
public static String[] splitPath(String path) {
Objects.requireNonNull(path);
String noRoot = path.substring(1);
int pkgStart = noRoot.indexOf("/");
String module = noRoot.substring(0, pkgStart);
List<String> result = new ArrayList<>();
result.add(module);
String pkg = noRoot.substring(pkgStart + 1);
String resName;
int pkgEnd = pkg.lastIndexOf("/");
if (pkgEnd == -1) { // No package.
resName = pkg;
} else {
resName = pkg.substring(pkgEnd + 1);
}
pkg = toPackage(pkg, false);
result.add(pkg);
result.add(resName);
String[] array = new String[result.size()];
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) {
return name.substring(0, index).replace('/', '.');
} else {
// ## unnamed package
if (log) {
System.err.format("Warning: %s in unnamed package%n", name);
}
return "";
}
}
}