Path: blob/master/test/langtools/tools/lib/toolbox/JarTask.java
41149 views
/*1* Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223package toolbox;2425import java.io.BufferedInputStream;26import java.io.ByteArrayInputStream;27import java.io.File;28import java.io.IOError;29import java.io.IOException;30import java.io.InputStream;31import java.io.OutputStream;32import java.net.URI;33import java.nio.file.FileVisitResult;34import java.nio.file.Files;35import java.nio.file.Path;36import java.nio.file.Paths;37import java.nio.file.SimpleFileVisitor;38import java.nio.file.attribute.BasicFileAttributes;39import java.util.ArrayList;40import java.util.Arrays;41import java.util.Collections;42import java.util.EnumSet;43import java.util.HashMap;44import java.util.LinkedHashSet;45import java.util.List;46import java.util.ListIterator;47import java.util.Map;48import java.util.Set;49import java.util.jar.Attributes;50import java.util.jar.JarEntry;51import java.util.jar.JarOutputStream;52import java.util.jar.Manifest;53import java.util.regex.Matcher;54import java.util.regex.Pattern;55import java.util.stream.Collectors;56import java.util.stream.Stream;57import javax.tools.FileObject;58import javax.tools.JavaFileManager;59import javax.tools.JavaFileObject;60import static toolbox.ToolBox.currDir;6162/**63* A task to configure and run the jar file utility.64*/65public class JarTask extends AbstractTask<JarTask> {66private Path jar;67private Manifest manifest;68private String classpath;69private String mainClass;70private Path baseDir;71private List<Path> paths;72private Set<FileObject> fileObjects;7374/**75* Creates a task to write jar files, using API mode.76* @param toolBox the {@code ToolBox} to use77*/78public JarTask(ToolBox toolBox) {79super(toolBox, Task.Mode.API);80paths = Collections.emptyList();81fileObjects = new LinkedHashSet<>();82}8384/**85* Creates a JarTask for use with a given jar file.86* @param toolBox the {@code ToolBox} to use87* @param path the file88*/89public JarTask(ToolBox toolBox, String path) {90this(toolBox);91jar = Paths.get(path);92}9394/**95* Creates a JarTask for use with a given jar file.96* @param toolBox the {@code ToolBox} to use97* @param path the file98*/99public JarTask(ToolBox toolBox, Path path) {100this(toolBox);101jar = path;102}103104/**105* Sets a manifest for the jar file.106* @param manifest the manifest107* @return this task object108*/109public JarTask manifest(Manifest manifest) {110this.manifest = manifest;111return this;112}113114/**115* Sets a manifest for the jar file.116* @param manifest a string containing the contents of the manifest117* @return this task object118* @throws IOException if there is a problem creating the manifest119*/120public JarTask manifest(String manifest) throws IOException {121this.manifest = new Manifest(new ByteArrayInputStream(manifest.getBytes()));122return this;123}124125/**126* Sets the classpath to be written to the {@code Class-Path}127* entry in the manifest.128* @param classpath the classpath129* @return this task object130*/131public JarTask classpath(String classpath) {132this.classpath = classpath;133return this;134}135136/**137* Sets the class to be written to the {@code Main-Class}138* entry in the manifest..139* @param mainClass the name of the main class140* @return this task object141*/142public JarTask mainClass(String mainClass) {143this.mainClass = mainClass;144return this;145}146147/**148* Sets the base directory for files to be written into the jar file.149* @param baseDir the base directory150* @return this task object151*/152public JarTask baseDir(String baseDir) {153this.baseDir = Paths.get(baseDir);154return this;155}156157/**158* Sets the base directory for files to be written into the jar file.159* @param baseDir the base directory160* @return this task object161*/162public JarTask baseDir(Path baseDir) {163this.baseDir = baseDir;164return this;165}166167/**168* Sets the files to be written into the jar file.169* @param files the files170* @return this task object171*/172public JarTask files(String... files) {173this.paths = Stream.of(files)174.map(file -> Paths.get(file))175.collect(Collectors.toList());176return this;177}178179/**180* Adds a set of file objects to be written into the jar file, by copying them181* from a Location in a JavaFileManager.182* The file objects to be written are specified by a series of paths;183* each path can be in one of the following forms:184* <ul>185* <li>The name of a class. For example, java.lang.Object.186* In this case, the corresponding .class file will be written to the jar file.187* <li>the name of a package followed by {@code .*}. For example, {@code java.lang.*}.188* In this case, all the class files in the specified package will be written to189* the jar file.190* <li>the name of a package followed by {@code .**}. For example, {@code java.lang.**}.191* In this case, all the class files in the specified package, and any subpackages192* will be written to the jar file.193* </ul>194*195* @param fm the file manager in which to find the file objects196* @param l the location in which to find the file objects197* @param paths the paths specifying the file objects to be copied198* @return this task object199* @throws IOException if errors occur while determining the set of file objects200*/201public JarTask files(JavaFileManager fm, JavaFileManager.Location l, String... paths)202throws IOException {203for (String p : paths) {204if (p.endsWith(".**"))205addPackage(fm, l, p.substring(0, p.length() - 3), true);206else if (p.endsWith(".*"))207addPackage(fm, l, p.substring(0, p.length() - 2), false);208else209addFile(fm, l, p);210}211return this;212}213214private void addPackage(JavaFileManager fm, JavaFileManager.Location l, String pkg, boolean recurse)215throws IOException {216for (JavaFileObject fo : fm.list(l, pkg, EnumSet.allOf(JavaFileObject.Kind.class), recurse)) {217fileObjects.add(fo);218}219}220221private void addFile(JavaFileManager fm, JavaFileManager.Location l, String path) throws IOException {222JavaFileObject fo = fm.getJavaFileForInput(l, path, JavaFileObject.Kind.CLASS);223fileObjects.add(fo);224}225226/**227* Provides limited jar command-like functionality.228* The supported commands are:229* <ul>230* <li> jar cf jarfile -C dir files...231* <li> jar cfm jarfile manifestfile -C dir files...232* </ul>233* Any values specified by other configuration methods will be ignored.234* @param args arguments in the style of those for the jar command235* @return a Result object containing the results of running the task236*/237public Task.Result run(String... args) {238if (args.length < 2)239throw new IllegalArgumentException();240241ListIterator<String> iter = Arrays.asList(args).listIterator();242String first = iter.next();243switch (first) {244case "cf":245jar = Paths.get(iter.next());246break;247case "cfm":248jar = Paths.get(iter.next());249try (InputStream in = Files.newInputStream(Paths.get(iter.next()))) {250manifest = new Manifest(in);251} catch (IOException e) {252throw new IOError(e);253}254break;255}256257if (iter.hasNext()) {258if (iter.next().equals("-C"))259baseDir = Paths.get(iter.next());260else261iter.previous();262}263264paths = new ArrayList<>();265while (iter.hasNext())266paths.add(Paths.get(iter.next()));267268return run();269}270271/**272* {@inheritDoc}273* @return the name "jar"274*/275@Override276public String name() {277return "jar";278}279280/**281* Creates a jar file with the arguments as currently configured.282* @return a Result object indicating the outcome of the compilation283* and the content of any output written to stdout, stderr, or the284* main stream by the compiler.285* @throws TaskError if the outcome of the task is not as expected.286*/287@Override288public Task.Result run() {289Manifest m = (manifest == null) ? new Manifest() : manifest;290Attributes mainAttrs = m.getMainAttributes();291if (mainClass != null)292mainAttrs.put(Attributes.Name.MAIN_CLASS, mainClass);293if (classpath != null)294mainAttrs.put(Attributes.Name.CLASS_PATH, classpath);295296AbstractTask.StreamOutput sysOut = new AbstractTask.StreamOutput(System.out, System::setOut);297AbstractTask.StreamOutput sysErr = new AbstractTask.StreamOutput(System.err, System::setErr);298299Map<Task.OutputKind, String> outputMap = new HashMap<>();300301try (OutputStream os = Files.newOutputStream(jar);302JarOutputStream jos = openJar(os, m)) {303writeFiles(jos);304writeFileObjects(jos);305} catch (IOException e) {306error("Exception while opening " + jar, e);307} finally {308outputMap.put(Task.OutputKind.STDOUT, sysOut.close());309outputMap.put(Task.OutputKind.STDERR, sysErr.close());310}311return checkExit(new Task.Result(toolBox, this, (errors == 0) ? 0 : 1, outputMap));312}313314private JarOutputStream openJar(OutputStream os, Manifest m) throws IOException {315if (m == null || m.getMainAttributes().isEmpty() && m.getEntries().isEmpty()) {316return new JarOutputStream(os);317} else {318if (m.getMainAttributes().get(Attributes.Name.MANIFEST_VERSION) == null)319m.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");320return new JarOutputStream(os, m);321}322}323324private void writeFiles(JarOutputStream jos) throws IOException {325Path base = (baseDir == null) ? currDir : baseDir;326for (Path path : paths) {327Files.walkFileTree(base.resolve(path), new SimpleFileVisitor<Path>() {328@Override329public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {330try {331String p = base.relativize(file)332.normalize()333.toString()334.replace(File.separatorChar, '/');335JarEntry e = new JarEntry(p);336jos.putNextEntry(e);337try {338jos.write(Files.readAllBytes(file));339} finally {340jos.closeEntry();341}342return FileVisitResult.CONTINUE;343} catch (IOException e) {344error("Exception while adding " + file + " to jar file", e);345return FileVisitResult.TERMINATE;346}347}348});349}350}351352private void writeFileObjects(JarOutputStream jos) throws IOException {353for (FileObject fo : fileObjects) {354String p = guessPath(fo);355JarEntry e = new JarEntry(p);356jos.putNextEntry(e);357try {358byte[] buf = new byte[1024];359try (BufferedInputStream in = new BufferedInputStream(fo.openInputStream())) {360int n;361while ((n = in.read(buf)) > 0)362jos.write(buf, 0, n);363} catch (IOException ex) {364error("Exception while adding " + fo.getName() + " to jar file", ex);365}366} finally {367jos.closeEntry();368}369}370}371372/*373* A jar: URL is of the form jar:URL!/<entry> where URL is a URL for the .jar file itself.374* In Symbol files (i.e. ct.sym) the underlying entry is prefixed META-INF/sym/<base>.375*/376private final Pattern jarEntry = Pattern.compile(".*!/(?:META-INF/sym/[^/]+/)?(.*)");377378/*379* A jrt: URL is of the form jrt:/<module>/<package>/<file>380*/381private final Pattern jrtEntry = Pattern.compile("/([^/]+)/(.*)");382383/*384* A file: URL is of the form file:/path/to/{modules,patches}/<module>/<package>/<file>385*/386private final Pattern fileEntry = Pattern.compile(".*/(?:modules|patches)/([^/]+)/(.*)");387388private String guessPath(FileObject fo) {389URI u = fo.toUri();390switch (u.getScheme()) {391case "jar": {392Matcher m = jarEntry.matcher(u.getSchemeSpecificPart());393if (m.matches()) {394return m.group(1);395}396break;397}398case "jrt": {399Matcher m = jrtEntry.matcher(u.getSchemeSpecificPart());400if (m.matches()) {401return m.group(2);402}403break;404}405case "file": {406Matcher m = fileEntry.matcher(u.getSchemeSpecificPart());407if (m.matches()) {408return m.group(2);409}410break;411}412}413throw new IllegalArgumentException(fo.getName() + "--" + fo.toUri());414}415416private void error(String message, Throwable t) {417toolBox.out.println("Error: " + message + ": " + t);418errors++;419}420421private int errors;422}423424425