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