aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/JarCreator.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java_tools/buildjar/java/com/google/devtools/build/buildjar/JarCreator.java')
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/buildjar/JarCreator.java200
1 files changed, 200 insertions, 0 deletions
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/JarCreator.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/JarCreator.java
new file mode 100644
index 0000000000..ead7ceb3dc
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/JarCreator.java
@@ -0,0 +1,200 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.buildjar;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+/**
+ * A class for creating Jar files. Allows normalization of Jar entries by setting their timestamp to
+ * the DOS epoch. All Jar entries are sorted alphabetically.
+ */
+public class JarCreator extends JarHelper {
+
+ // Map from Jar entry names to files. Use TreeMap so we can establish a canonical order for the
+ // entries regardless in what order they get added.
+ private final Map<String, String> jarEntries = new TreeMap<>();
+ private String manifestFile;
+ private String mainClass;
+
+ public JarCreator(String fileName) {
+ super(fileName);
+ }
+
+ /**
+ * Adds an entry to the Jar file, normalizing the name.
+ *
+ * @param entryName the name of the entry in the Jar file
+ * @param fileName the name of the input file for the entry
+ * @return true iff a new entry was added
+ */
+ public boolean addEntry(String entryName, String fileName) {
+ if (entryName.startsWith("/")) {
+ entryName = entryName.substring(1);
+ } else if (entryName.startsWith("./")) {
+ entryName = entryName.substring(2);
+ }
+ return jarEntries.put(entryName, fileName) == null;
+ }
+
+ /**
+ * Adds the contents of a directory to the Jar file. All files below this
+ * directory will be added to the Jar file using the name relative to the
+ * directory as the name for the Jar entry.
+ *
+ * @param directory the directory to add to the jar
+ */
+ public void addDirectory(String directory) {
+ addDirectory(null, new File(directory));
+ }
+
+ /**
+ * Adds the contents of a directory to the Jar file. All files below this
+ * directory will be added to the Jar file using the prefix and the name
+ * relative to the directory as the name for the Jar entry. Always uses '/' as
+ * the separator char for the Jar entries.
+ *
+ * @param prefix the prefix to prepend to every Jar entry name found below the
+ * directory
+ * @param directory the directory to add to the Jar
+ */
+ private void addDirectory(String prefix, File directory) {
+ File[] files = directory.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ String entryName = prefix != null ? prefix + "/" + file.getName() : file.getName();
+ jarEntries.put(entryName, file.getAbsolutePath());
+ if (file.isDirectory()) {
+ addDirectory(entryName, file);
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds a collection of entries to the jar, each with a given source path, and with
+ * the resulting file in the root of the jar.
+ * <pre>
+ * some/long/path.foo => (path.foo, some/long/path.foo)
+ * </pre>
+ */
+ public void addRootEntries(Collection<String> entries) {
+ for (String entry : entries) {
+ jarEntries.put(new File(entry).getName(), entry);
+ }
+ }
+
+ /**
+ * Sets the main.class entry for the manifest. A value of <code>null</code>
+ * (the default) will omit the entry.
+ *
+ * @param mainClass the fully qualified name of the main class
+ */
+ public void setMainClass(String mainClass) {
+ this.mainClass = mainClass;
+ }
+
+ /**
+ * Sets filename for the manifest content. If this is set the manifest will be
+ * read from this file otherwise the manifest content will get generated on
+ * the fly.
+ *
+ * @param manifestFile the filename of the manifest file.
+ */
+ public void setManifestFile(String manifestFile) {
+ this.manifestFile = manifestFile;
+ }
+
+ private byte[] manifestContent() throws IOException {
+ Manifest manifest;
+ if (manifestFile != null) {
+ FileInputStream in = new FileInputStream(manifestFile);
+ manifest = new Manifest(in);
+ } else {
+ manifest = new Manifest();
+ }
+ Attributes attributes = manifest.getMainAttributes();
+ attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ Attributes.Name createdBy = new Attributes.Name("Created-By");
+ if (attributes.getValue(createdBy) == null) {
+ attributes.put(createdBy, "blaze");
+ }
+ if (mainClass != null) {
+ attributes.put(Attributes.Name.MAIN_CLASS, mainClass);
+ }
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ manifest.write(out);
+ return out.toByteArray();
+ }
+
+ /**
+ * Executes the creation of the Jar file.
+ *
+ * @throws IOException if the Jar cannot be written or any of the entries
+ * cannot be read.
+ */
+ public void execute() throws IOException {
+ out = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(jarFile)));
+
+ // Create the manifest entry in the Jar file
+ writeManifestEntry(manifestContent());
+ try {
+ for (Map.Entry<String, String> entry : jarEntries.entrySet()) {
+ copyEntry(entry.getKey(), new File(entry.getValue()));
+ }
+ } finally {
+ out.closeEntry();
+ out.close();
+ }
+ }
+
+ /**
+ * A simple way to create Jar file using the JarCreator class.
+ */
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ System.err.println("usage: CreateJar output [root directories]");
+ System.exit(1);
+ }
+ String output = args[0];
+ JarCreator createJar = new JarCreator(output);
+ for (int i = 1; i < args.length; i++) {
+ createJar.addDirectory(args[i]);
+ }
+ createJar.setCompression(true);
+ createJar.setNormalize(true);
+ createJar.setVerbose(true);
+ long start = System.currentTimeMillis();
+ try {
+ createJar.execute();
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.err.println(e.getMessage());
+ System.exit(1);
+ }
+ long stop = System.currentTimeMillis();
+ System.err.println((stop - start) + "ms.");
+ }
+}