| /* |
| * 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; |
| } |
| } |
| |
| } |