Version 1.7.4
[gae.git] / java / src / main / com / google / appengine / tools / util / JarTool.java
blobfc045b6149e3eaa1ca29f535ed36d9fa42f55dbe
1 // Copyright 2012 Google Inc. All Rights Reserved.
3 package com.google.appengine.tools.util;
5 import java.io.File;
6 import java.io.FileInputStream;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.util.ArrayDeque;
10 import java.util.Arrays;
11 import java.util.Comparator;
12 import java.util.Deque;
13 import java.util.Set;
14 import java.util.jar.JarEntry;
15 import java.util.jar.Manifest;
17 /**
18 * A utility for building multiple jar files, each of size less than a specified maximum, from a
19 * directory.
20 * <p>
21 * Usage: Construct an instance and then invoke the method {@link #run()}.
22 * <p>
23 * A note on ordering: The files from the input directory will be split across multiple jar files.
24 * In order to facilitate being able to find a file from the input directory in the set of output
25 * jar files, the input files are added to the jar files in package order. For example:
26 * <ol>
27 * <li>{@code A.class}
28 * <li>{@code B.class}
29 * <li>{@code ant/A.class}
30 * <li>{@code ant/B.class}
31 * <li>{@code ant/axe/A.class}
32 * <li>{@code ant/axe/B.class}
33 * <li>{@code ball/A.class}
34 * </ol>
37 public class JarTool extends JarMaker {
39 private Deque<File> stack = new ArrayDeque<File>(1000);
40 private int prefixLength;
42 /**
43 * Constructor
45 * @param baseName The base name of the emitted jar files. The file name will also include a
46 * suffix containing four numerical digits.
47 * @param inputDirectory The directory whose contents should be included in the emitted jar files.
48 * @param outputDirectory The directory into which to emit the jar files. This directory will be
49 * created if it does not exist.
50 * @param maximumSize The maximum size of a jar file that should be emitted, in bytes. Note that
51 * the actual size of the jar files may be less than this value due to compression. The
52 * compression ratio is not specified. (In practice it is frequently 50%).
53 * @param excludes A set file-name suffixes. If this is not {@code null} then {@link JarEntry
54 * JarEntries} whose path includes one of these suffixes will be excluded from the
55 * generated jar files.
57 public JarTool(String baseName, File inputDirectory, File outputDirectory, int maximumSize,
58 Set<String> excludes) {
59 super(baseName, 4, outputDirectory, maximumSize, excludes, true);
60 if (!inputDirectory.isDirectory()) {
61 throw new IllegalArgumentException(inputDirectory.getPath() + " is not a directory.");
63 stack.push(inputDirectory);
64 prefixLength = getZipCompatiblePath(inputDirectory).length();
67 @Override
68 protected JarEntryData getNextJarEntry() throws IOException {
69 while (true) {
70 File nextFile = stack.pollFirst();
71 if (nextFile == null) {
72 return null;
74 if (nextFile.isDirectory()) {
75 for (File f : sortedListing(nextFile)) {
76 if (f.isDirectory() || shouldIncludeFile(f.getName())) {
77 stack.push(f);
80 } else {
81 JarEntry jarEntry = new JarEntry(getZipCompatiblePath(nextFile).substring(prefixLength));
82 jarEntry.setTime(0L);
83 InputStream inputStream = new FileInputStream(nextFile);
84 return new JarEntryData(jarEntry, inputStream);
89 private static String getZipCompatiblePath(File f) {
90 String path = f.getPath();
91 path = path.replace("\\", "/");
92 if (f.isDirectory() && !path.endsWith("/")) {
93 path += "/";
95 return path;
98 @Override
99 protected Manifest getManifest() {
100 return null;
104 * Returns the listing of a directory sorted so that directories precede files, and files and
105 * directories are sorted <b>decreasing</b> by name. The output from this method will be pushed
106 * onto a FIFO stack and visited in the reverse order. The result is that we will be visiting a
107 * tree of Java classes in package order.
109 private static File[] sortedListing(File dir) {
110 File[] contents = dir.listFiles();
111 Comparator<File> comparator = new Comparator<File>() {
113 @Override
114 public int compare(File f1, File f2) {
115 if (f1.isDirectory() && f2.isFile()) {
116 return -1;
118 if (f1.isFile() && f2.isDirectory()) {
119 return 1;
121 return f2.getName().compareTo(f1.getName());
125 Arrays.sort(contents, comparator);
126 return contents;