You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
332 lines
11 KiB
332 lines
11 KiB
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You 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 org.apache.harmony.pack200;
|
|
|
|
import java.io.BufferedOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
import java.util.ArrayList;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.jar.JarEntry;
|
|
import java.util.jar.JarFile;
|
|
import java.util.jar.JarInputStream;
|
|
import java.util.zip.GZIPOutputStream;
|
|
|
|
/**
|
|
* Archive is the main entry point to pack200 and represents a packed archive.
|
|
* An archive is constructed with either a JarInputStream and an output stream
|
|
* or a JarFile as input and an OutputStream. Options can be set, then
|
|
* <code>pack()</code> is called, to pack the Jar file into a pack200 archive.
|
|
*/
|
|
public class Archive {
|
|
|
|
private final JarInputStream jarInputStream;
|
|
private final OutputStream outputStream;
|
|
private JarFile jarFile;
|
|
private long currentSegmentSize;
|
|
private final PackingOptions options;
|
|
|
|
/**
|
|
* Creates an Archive with streams for the input and output.
|
|
*
|
|
* @param inputStream
|
|
* @param outputStream
|
|
* @param options - packing options (if null then defaults are used)
|
|
* @throws IOException
|
|
*/
|
|
public Archive(JarInputStream inputStream, OutputStream outputStream,
|
|
PackingOptions options) throws IOException {
|
|
jarInputStream = inputStream;
|
|
if(options == null) {
|
|
// use all defaults
|
|
options = new PackingOptions();
|
|
}
|
|
this.options = options;
|
|
if (options.isGzip()) {
|
|
outputStream = new GZIPOutputStream(outputStream);
|
|
}
|
|
this.outputStream = new BufferedOutputStream(outputStream);
|
|
PackingUtils.config(options);
|
|
}
|
|
|
|
/**
|
|
* Creates an Archive with the given input file and a stream for the output
|
|
*
|
|
* @param jarFile - the input file
|
|
* @param outputStream
|
|
* @param options - packing options (if null then defaults are used)
|
|
* @throws IOException
|
|
*/
|
|
public Archive(JarFile jarFile, OutputStream outputStream,
|
|
PackingOptions options) throws IOException {
|
|
if(options == null) { // use all defaults
|
|
options = new PackingOptions();
|
|
}
|
|
this.options = options;
|
|
if (options.isGzip()) {
|
|
outputStream = new GZIPOutputStream(outputStream);
|
|
}
|
|
this.outputStream = new BufferedOutputStream(outputStream);
|
|
this.jarFile = jarFile;
|
|
jarInputStream = null;
|
|
PackingUtils.config(options);
|
|
}
|
|
|
|
/**
|
|
* Pack the archive
|
|
* @throws Pack200Exception
|
|
* @throws IOException
|
|
*/
|
|
public void pack() throws Pack200Exception, IOException {
|
|
if (0 == options.getEffort()) {
|
|
doZeroEffortPack();
|
|
} else {
|
|
doNormalPack();
|
|
}
|
|
}
|
|
|
|
private void doZeroEffortPack() throws IOException, Pack200Exception {
|
|
PackingUtils.log("Start to perform a zero-effort packing");
|
|
if (jarInputStream != null) {
|
|
PackingUtils.copyThroughJar(jarInputStream, outputStream);
|
|
} else {
|
|
PackingUtils.copyThroughJar(jarFile, outputStream);
|
|
}
|
|
}
|
|
|
|
private void doNormalPack() throws IOException, Pack200Exception {
|
|
PackingUtils.log("Start to perform a normal packing");
|
|
List packingFileList;
|
|
if (jarInputStream != null) {
|
|
packingFileList = PackingUtils.getPackingFileListFromJar(
|
|
jarInputStream, options.isKeepFileOrder());
|
|
} else {
|
|
packingFileList = PackingUtils.getPackingFileListFromJar(jarFile,
|
|
options.isKeepFileOrder());
|
|
}
|
|
|
|
List segmentUnitList = splitIntoSegments(packingFileList);
|
|
int previousByteAmount = 0;
|
|
int packedByteAmount = 0;
|
|
|
|
int segmentSize = segmentUnitList.size();
|
|
SegmentUnit segmentUnit;
|
|
for (int index = 0; index < segmentSize; index++) {
|
|
segmentUnit = (SegmentUnit) segmentUnitList.get(index);
|
|
new Segment().pack(segmentUnit, outputStream, options);
|
|
previousByteAmount += segmentUnit.getByteAmount();
|
|
packedByteAmount += segmentUnit.getPackedByteAmount();
|
|
}
|
|
|
|
PackingUtils.log("Total: Packed " + previousByteAmount
|
|
+ " input bytes of " + packingFileList.size() + " files into "
|
|
+ packedByteAmount + " bytes in " + segmentSize + " segments");
|
|
|
|
outputStream.close();
|
|
}
|
|
|
|
private List splitIntoSegments(List packingFileList) throws IOException,
|
|
Pack200Exception {
|
|
List segmentUnitList = new ArrayList();
|
|
List classes = new ArrayList();
|
|
List files = new ArrayList();
|
|
long segmentLimit = options.getSegmentLimit();
|
|
|
|
int size = packingFileList.size();
|
|
PackingFile packingFile;
|
|
for (int index = 0; index < size; index++) {
|
|
packingFile = (PackingFile) packingFileList.get(index);
|
|
if (!addJarEntry(packingFile, classes, files)) {
|
|
// not added because segment has reached maximum size
|
|
segmentUnitList.add(new SegmentUnit(classes, files));
|
|
classes = new ArrayList();
|
|
files = new ArrayList();
|
|
currentSegmentSize = 0;
|
|
// add the jar to a new segment
|
|
addJarEntry(packingFile, classes, files);
|
|
// ignore the size of first entry for compatibility with RI
|
|
currentSegmentSize = 0;
|
|
} else if (segmentLimit == 0 && estimateSize(packingFile) > 0) {
|
|
// create a new segment for each class unless size is 0
|
|
segmentUnitList.add(new SegmentUnit(classes, files));
|
|
classes = new ArrayList();
|
|
files = new ArrayList();
|
|
}
|
|
}
|
|
if (classes.size() > 0 && files.size() > 0) {
|
|
segmentUnitList.add(new SegmentUnit(classes, files));
|
|
}
|
|
return segmentUnitList;
|
|
}
|
|
|
|
private boolean addJarEntry(PackingFile packingFile, List javaClasses,
|
|
List files) throws IOException, Pack200Exception {
|
|
long segmentLimit = options.getSegmentLimit();
|
|
if (segmentLimit != -1 && segmentLimit != 0) {
|
|
// -1 is a special case where only one segment is created and
|
|
// 0 is a special case where one segment is created for each file
|
|
// except for files in "META-INF"
|
|
long packedSize = estimateSize(packingFile);
|
|
if (packedSize + currentSegmentSize > segmentLimit
|
|
&& currentSegmentSize > 0) {
|
|
// don't add this JarEntry to the current segment
|
|
return false;
|
|
} else {
|
|
// do add this JarEntry
|
|
currentSegmentSize += packedSize;
|
|
}
|
|
}
|
|
|
|
String name = packingFile.getName();
|
|
if (name.endsWith(".class") && !options.isPassFile(name)) {
|
|
Pack200ClassReader classParser = new Pack200ClassReader(
|
|
packingFile.contents);
|
|
classParser.setFileName(name);
|
|
javaClasses.add(classParser);
|
|
packingFile.contents = new byte[0];
|
|
}
|
|
files.add(packingFile);
|
|
return true;
|
|
}
|
|
|
|
private long estimateSize(PackingFile packingFile) {
|
|
// The heuristic used here is for compatibility with the RI and should
|
|
// not be changed
|
|
String name = packingFile.getName();
|
|
if (name.startsWith("META-INF") || name.startsWith("/META-INF")) {
|
|
return 0;
|
|
} else {
|
|
long fileSize = packingFile.contents.length;
|
|
if (fileSize < 0) {
|
|
fileSize = 0;
|
|
}
|
|
return name.length() + fileSize + 5;
|
|
}
|
|
}
|
|
|
|
static class SegmentUnit {
|
|
|
|
private final List classList;
|
|
|
|
private final List fileList;
|
|
|
|
private int byteAmount = 0;
|
|
|
|
private int packedByteAmount = 0;
|
|
|
|
public SegmentUnit(List classes, List files) {
|
|
classList = classes;
|
|
fileList = files;
|
|
|
|
// Calculate the amount of bytes in classes and files before packing
|
|
Pack200ClassReader classReader;
|
|
for (Iterator iterator = classList.iterator(); iterator.hasNext();) {
|
|
classReader = (Pack200ClassReader) iterator.next();
|
|
byteAmount += classReader.b.length;
|
|
}
|
|
|
|
PackingFile file;
|
|
for (Iterator iterator = fileList.iterator(); iterator.hasNext();) {
|
|
file = (PackingFile) iterator.next();
|
|
byteAmount += file.contents.length;
|
|
}
|
|
}
|
|
|
|
public List getClassList() {
|
|
return classList;
|
|
}
|
|
|
|
public int classListSize() {
|
|
return classList.size();
|
|
}
|
|
|
|
public int fileListSize() {
|
|
return fileList.size();
|
|
}
|
|
|
|
public List getFileList() {
|
|
return fileList;
|
|
}
|
|
|
|
public int getByteAmount() {
|
|
return byteAmount;
|
|
}
|
|
|
|
public int getPackedByteAmount() {
|
|
return packedByteAmount;
|
|
}
|
|
|
|
public void addPackedByteAmount(int amount) {
|
|
packedByteAmount += amount;
|
|
}
|
|
}
|
|
|
|
static class PackingFile {
|
|
|
|
private final String name;
|
|
private byte[] contents;
|
|
private final long modtime;
|
|
private final boolean deflateHint;
|
|
private final boolean isDirectory;
|
|
|
|
public PackingFile(String name, byte[] contents, long modtime) {
|
|
this.name = name;
|
|
this.contents = contents;
|
|
this.modtime = modtime;
|
|
deflateHint = false;
|
|
isDirectory = false;
|
|
}
|
|
|
|
public PackingFile(byte[] bytes, JarEntry jarEntry) {
|
|
name = jarEntry.getName();
|
|
contents = bytes;
|
|
modtime = jarEntry.getTime();
|
|
deflateHint = jarEntry.getMethod() == JarEntry.DEFLATED;
|
|
isDirectory = jarEntry.isDirectory();
|
|
}
|
|
|
|
public byte[] getContents() {
|
|
return contents;
|
|
}
|
|
|
|
public String getName() {
|
|
return name;
|
|
}
|
|
|
|
public long getModtime() {
|
|
return modtime;
|
|
}
|
|
|
|
public void setContents(byte[] contents) {
|
|
this.contents = contents;
|
|
}
|
|
|
|
public boolean isDefalteHint() {
|
|
return deflateHint;
|
|
}
|
|
|
|
public boolean isDirectory(){
|
|
return isDirectory;
|
|
}
|
|
|
|
public String toString() {
|
|
return name;
|
|
}
|
|
}
|
|
|
|
}
|
|
|