blob: d1839d3eb4f50d7dd24b60bba27c8bac09b56015 [file] [log] [blame]
/*
* Copyright (c) 2015, 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.plugins;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import jdk.tools.jlink.internal.ModuleEntryImpl;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ModuleEntry;
import jdk.tools.jlink.plugin.ModulePool;
import jdk.tools.jlink.plugin.TransformerPlugin;
import jdk.tools.jlink.internal.Utils;
/**
*
* Copy files to image from various locations.
*/
public class FileCopierPlugin implements TransformerPlugin {
public static final String NAME = "copy-files";
private static final class CopiedFile {
Path source;
Path target;
}
public static final String FAKE_MODULE = "$jlink-file-copier";
private final List<CopiedFile> files = new ArrayList<>();
/**
* Symbolic link to another path.
*/
public static abstract class SymImageFile extends ModuleEntryImpl {
private final String targetPath;
public SymImageFile(String targetPath, String module, String path,
ModuleEntry.Type type, InputStream stream, long size) {
super(module, path, type, stream, size);
this.targetPath = targetPath;
}
public String getTargetPath() {
return targetPath;
}
}
private static final class SymImageFileImpl extends SymImageFile {
public SymImageFileImpl(String targetPath, Path file, String module,
String path, ModuleEntry.Type type) {
super(targetPath, module, path, type, newStream(file), length(file));
}
}
private static long length(Path file) {
try {
return Files.size(file);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private static InputStream newStream(Path file) {
try {
return Files.newInputStream(file);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
private static final class DirectoryCopy implements FileVisitor<Path> {
private final Path source;
private final ModulePool pool;
private final String targetDir;
private final List<SymImageFile> symlinks = new ArrayList<>();
DirectoryCopy(Path source, ModulePool pool, String targetDir) {
this.source = source;
this.pool = pool;
this.targetDir = targetDir;
}
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs) throws IOException {
Objects.requireNonNull(file);
Objects.requireNonNull(attrs);
String path = targetDir + "/" + source.relativize(file);
if (attrs.isSymbolicLink()) {
Path symTarget = Files.readSymbolicLink(file);
if (!Files.exists(symTarget)) {
// relative to file parent?
Path parent = file.getParent();
if (parent != null) {
symTarget = parent.resolve(symTarget);
}
}
if (!Files.exists(symTarget)) {
System.err.println("WARNING: Skipping sym link, target "
+ Files.readSymbolicLink(file) + "not found");
return FileVisitResult.CONTINUE;
}
SymImageFileImpl impl = new SymImageFileImpl(symTarget.toString(),
file, path, Objects.requireNonNull(file.getFileName()).toString(),
ModuleEntry.Type.OTHER);
symlinks.add(impl);
} else {
addFile(pool, file, path);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
if (exc != null) {
throw exc;
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc)
throws IOException {
throw exc;
}
}
private static void addFile(ModulePool pool, Path file, String path)
throws IOException {
Objects.requireNonNull(pool);
Objects.requireNonNull(file);
Objects.requireNonNull(path);
ModuleEntry impl = ModuleEntry.create(FAKE_MODULE,
"/" + FAKE_MODULE + "/other/" + path,
ModuleEntry.Type.OTHER, newStream(file), length(file));
try {
pool.add(impl);
} catch (Exception ex) {
throw new IOException(ex);
}
}
@Override
public Set<Category> getType() {
Set<Category> set = new HashSet<>();
set.add(Category.TRANSFORMER);
return Collections.unmodifiableSet(set);
}
@Override
public void configure(Map<String, String> config) {
List<String> arguments = Utils.parseList(config.get(NAME));
if (arguments.isEmpty()) {
throw new RuntimeException("Invalid argument for " + NAME);
}
String javahome = System.getProperty("java.home");
for (String a : arguments) {
int i = a.indexOf("=");
CopiedFile cf = new CopiedFile();
if (i == -1) {
Path file = Paths.get(a);
if (file.isAbsolute()) {
cf.source = file;
// The target is the image root directory.
cf.target = file.getFileName();
} else {
file = new File(javahome, a).toPath();
cf.source = file;
cf.target = Paths.get(a);
}
} else {
String target = a.substring(i + 1);
String f = a.substring(0, i);
Path file = Paths.get(f);
if (file.isAbsolute()) {
cf.source = file;
} else {
cf.source = new File(javahome,
file.toFile().getPath()).toPath();
}
cf.target = Paths.get(target);
}
if (!Files.exists(cf.source)) {
System.err.println("Skipping file " + cf.source
+ ", it doesn't exist");
} else {
files.add(cf);
}
}
}
@Override
public void visit(ModulePool in, ModulePool out) {
in.transformAndCopy((file) -> {
return file;
}, out);
// Add new files.
try {
for (CopiedFile file : files) {
if (Files.isRegularFile(file.source)) {
addFile(out, file.source, file.target.toString());
} else if (Files.isDirectory(file.source)) {
DirectoryCopy dc = new DirectoryCopy(file.source,
out, file.target.toString());
Files.walkFileTree(file.source, dc);
// Add symlinks after actual content
for (SymImageFile imf : dc.symlinks) {
try {
out.add(imf);
} catch (Exception ex) {
throw new PluginException(ex);
}
}
}
}
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
@Override
public String getName() {
return NAME;
}
@Override
public String getDescription() {
return PluginsResourceBundle.getDescription(NAME);
}
@Override
public boolean hasArguments() {
return true;
}
@Override
public String getArgumentsDescription() {
return PluginsResourceBundle.getArgument(NAME);
}
}