1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.compress.harmony.pack200;
18
19 import java.io.BufferedOutputStream;
20 import java.io.IOException;
21 import java.io.OutputStream;
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.jar.JarEntry;
25 import java.util.jar.JarFile;
26 import java.util.jar.JarInputStream;
27 import java.util.zip.GZIPOutputStream;
28 import java.util.zip.ZipEntry;
29
30
31
32
33
34 public class Archive {
35
36 static class PackingFile {
37
38 private final String name;
39 private byte[] contents;
40 private final long modtime;
41 private final boolean deflateHint;
42 private final boolean isDirectory;
43
44 PackingFile(final byte[] bytes, final JarEntry jarEntry) {
45 name = jarEntry.getName();
46 contents = bytes;
47 modtime = jarEntry.getTime();
48 deflateHint = jarEntry.getMethod() == ZipEntry.DEFLATED;
49 isDirectory = jarEntry.isDirectory();
50 }
51
52 PackingFile(final String name, final byte[] contents, final long modtime) {
53 this.name = name;
54 this.contents = contents;
55 this.modtime = modtime;
56 deflateHint = false;
57 isDirectory = false;
58 }
59
60 public byte[] getContents() {
61 return contents;
62 }
63
64 public long getModtime() {
65 return modtime;
66 }
67
68 public String getName() {
69 return name;
70 }
71
72 public boolean isDefalteHint() {
73 return deflateHint;
74 }
75
76 public boolean isDirectory() {
77 return isDirectory;
78 }
79
80 public void setContents(final byte[] contents) {
81 this.contents = contents;
82 }
83
84 @Override
85 public String toString() {
86 return name;
87 }
88 }
89
90 static class SegmentUnit {
91
92 private final List<Pack200ClassReader> classList;
93
94 private final List<PackingFile> fileList;
95
96 private int byteAmount;
97
98 private int packedByteAmount;
99
100 SegmentUnit(final List<Pack200ClassReader> classes, final List<PackingFile> files) {
101 classList = classes;
102 fileList = files;
103 byteAmount = 0;
104
105 byteAmount += classList.stream().mapToInt(element -> element.b.length).sum();
106 byteAmount += fileList.stream().mapToInt(element -> element.contents.length).sum();
107 }
108
109 public void addPackedByteAmount(final int amount) {
110 packedByteAmount += amount;
111 }
112
113 public int classListSize() {
114 return classList.size();
115 }
116
117 public int fileListSize() {
118 return fileList.size();
119 }
120
121 public int getByteAmount() {
122 return byteAmount;
123 }
124
125 public List<Pack200ClassReader> getClassList() {
126 return classList;
127 }
128
129 public List<PackingFile> getFileList() {
130 return fileList;
131 }
132
133 public int getPackedByteAmount() {
134 return packedByteAmount;
135 }
136 }
137
138 private static final byte[] EMPTY_BYTE_ARRAY = {};
139
140 private final JarInputStream jarInputStream;
141 private final OutputStream outputStream;
142 private JarFile jarFile;
143
144 private long currentSegmentSize;
145
146 private final PackingOptions options;
147
148
149
150
151
152
153
154
155
156 public Archive(final JarFile jarFile, OutputStream outputStream, PackingOptions options) throws IOException {
157 if (options == null) {
158 options = new PackingOptions();
159 }
160 this.options = options;
161 if (options.isGzip()) {
162 outputStream = new GZIPOutputStream(outputStream);
163 }
164 this.outputStream = new BufferedOutputStream(outputStream);
165 this.jarFile = jarFile;
166 jarInputStream = null;
167 PackingUtils.config(options);
168 }
169
170
171
172
173
174
175
176
177
178 public Archive(final JarInputStream inputStream, OutputStream outputStream, PackingOptions options) throws IOException {
179 jarInputStream = inputStream;
180 if (options == null) {
181
182 options = new PackingOptions();
183 }
184 this.options = options;
185 if (options.isGzip()) {
186 outputStream = new GZIPOutputStream(outputStream);
187 }
188 this.outputStream = new BufferedOutputStream(outputStream);
189 PackingUtils.config(options);
190 }
191
192 private boolean addJarEntry(final PackingFile packingFile, final List<Pack200ClassReader> javaClasses, final List<PackingFile> files) {
193 final long segmentLimit = options.getSegmentLimit();
194 if (segmentLimit != -1 && segmentLimit != 0) {
195
196
197
198 final long packedSize = estimateSize(packingFile);
199 if (packedSize + currentSegmentSize > segmentLimit && currentSegmentSize > 0) {
200
201 return false;
202 }
203
204 currentSegmentSize += packedSize;
205 }
206
207 final String name = packingFile.getName();
208 if (name.endsWith(".class") && !options.isPassFile(name)) {
209 final Pack200ClassReader classParser = new Pack200ClassReader(packingFile.contents);
210 classParser.setFileName(name);
211 javaClasses.add(classParser);
212 packingFile.contents = EMPTY_BYTE_ARRAY;
213 }
214 files.add(packingFile);
215 return true;
216 }
217
218 private void doNormalPack() throws IOException, Pack200Exception {
219 PackingUtils.log("Start to perform a normal packing");
220 List<PackingFile> packingFileList;
221 if (jarInputStream != null) {
222 packingFileList = PackingUtils.getPackingFileListFromJar(jarInputStream, options.isKeepFileOrder());
223 } else {
224 packingFileList = PackingUtils.getPackingFileListFromJar(jarFile, options.isKeepFileOrder());
225 }
226
227 final List<SegmentUnit> segmentUnitList = splitIntoSegments(packingFileList);
228 int previousByteAmount = 0;
229 int packedByteAmount = 0;
230
231 final int segmentSize = segmentUnitList.size();
232 SegmentUnit segmentUnit;
233 for (int index = 0; index < segmentSize; index++) {
234 segmentUnit = segmentUnitList.get(index);
235 new Segment().pack(segmentUnit, outputStream, options);
236 previousByteAmount += segmentUnit.getByteAmount();
237 packedByteAmount += segmentUnit.getPackedByteAmount();
238 }
239
240 PackingUtils.log("Total: Packed " + previousByteAmount + " input bytes of " + packingFileList.size() + " files into " + packedByteAmount + " bytes in "
241 + segmentSize + " segments");
242
243 outputStream.close();
244 }
245
246 private void doZeroEffortPack() throws IOException {
247 PackingUtils.log("Start to perform a zero-effort packing");
248 if (jarInputStream != null) {
249 PackingUtils.copyThroughJar(jarInputStream, outputStream);
250 } else {
251 PackingUtils.copyThroughJar(jarFile, outputStream);
252 }
253 }
254
255 private long estimateSize(final PackingFile packingFile) {
256
257
258 final String name = packingFile.getName();
259 if (name.startsWith("META-INF") || name.startsWith("/META-INF")) {
260 return 0;
261 }
262 long fileSize = packingFile.contents.length;
263 if (fileSize < 0) {
264 fileSize = 0;
265 }
266 return name.length() + fileSize + 5;
267 }
268
269
270
271
272
273
274
275 public void pack() throws Pack200Exception, IOException {
276 if (0 == options.getEffort()) {
277 doZeroEffortPack();
278 } else {
279 doNormalPack();
280 }
281 }
282
283 private List<SegmentUnit> splitIntoSegments(final List<PackingFile> packingFileList) {
284 final List<SegmentUnit> segmentUnitList = new ArrayList<>();
285 List<Pack200ClassReader> classes = new ArrayList<>();
286 List<PackingFile> files = new ArrayList<>();
287 final long segmentLimit = options.getSegmentLimit();
288
289 final int size = packingFileList.size();
290 PackingFile packingFile;
291 for (int index = 0; index < size; index++) {
292 packingFile = packingFileList.get(index);
293 if (!addJarEntry(packingFile, classes, files)) {
294
295 segmentUnitList.add(new SegmentUnit(classes, files));
296 classes = new ArrayList<>();
297 files = new ArrayList<>();
298 currentSegmentSize = 0;
299
300 addJarEntry(packingFile, classes, files);
301
302 currentSegmentSize = 0;
303 } else if (segmentLimit == 0 && estimateSize(packingFile) > 0) {
304
305 segmentUnitList.add(new SegmentUnit(classes, files));
306 classes = new ArrayList<>();
307 files = new ArrayList<>();
308 }
309 }
310
311
312 if (classes.size() > 0 || files.size() > 0) {
313 segmentUnitList.add(new SegmentUnit(classes, files));
314 }
315 return segmentUnitList;
316 }
317
318 }