1 // Copyright 2012 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.tools
.util
;
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
;
14 import java
.util
.jar
.JarEntry
;
15 import java
.util
.jar
.Manifest
;
18 * A utility for building multiple jar files, each of size less than a specified maximum, from a
21 * Usage: Construct an instance and then invoke the method {@link #run()}.
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:
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}
37 public class JarTool
extends JarMaker
{
39 private Deque
<File
> stack
= new ArrayDeque
<File
>(1000);
40 private int prefixLength
;
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();
68 protected JarEntryData
getNextJarEntry() throws IOException
{
70 File nextFile
= stack
.pollFirst();
71 if (nextFile
== null) {
74 if (nextFile
.isDirectory()) {
75 for (File f
: sortedListing(nextFile
)) {
76 if (f
.isDirectory() || shouldIncludeFile(f
.getName())) {
81 JarEntry jarEntry
= new JarEntry(getZipCompatiblePath(nextFile
).substring(prefixLength
));
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("/")) {
99 protected Manifest
getManifest() {
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
>() {
114 public int compare(File f1
, File f2
) {
115 if (f1
.isDirectory() && f2
.isFile()) {
118 if (f1
.isFile() && f2
.isDirectory()) {
121 return f2
.getName().compareTo(f1
.getName());
125 Arrays
.sort(contents
, comparator
);