001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.compress.harmony.pack200; 018 019import java.io.BufferedOutputStream; 020import java.io.IOException; 021import java.io.OutputStream; 022import java.util.ArrayList; 023import java.util.Iterator; 024import java.util.List; 025import java.util.jar.JarEntry; 026import java.util.jar.JarFile; 027import java.util.jar.JarInputStream; 028import java.util.zip.GZIPOutputStream; 029import java.util.zip.ZipEntry; 030 031/** 032 * Archive is the main entry point to pack200 and represents a packed archive. An archive is constructed with either a 033 * JarInputStream and an output stream or a JarFile as input and an OutputStream. Options can be set, then 034 * <code>pack()</code> is called, to pack the Jar file into a pack200 archive. 035 */ 036public class Archive { 037 038 private final JarInputStream jarInputStream; 039 private final OutputStream outputStream; 040 private JarFile jarFile; 041 private long currentSegmentSize; 042 private final PackingOptions options; 043 044 /** 045 * Creates an Archive with streams for the input and output. 046 * 047 * @param inputStream TODO 048 * @param outputStream TODO 049 * @param options - packing options (if null then defaults are used) 050 * @throws IOException If an I/O error occurs. 051 */ 052 public Archive(final JarInputStream inputStream, OutputStream outputStream, PackingOptions options) 053 throws IOException { 054 jarInputStream = inputStream; 055 if (options == null) { 056 // use all defaults 057 options = new PackingOptions(); 058 } 059 this.options = options; 060 if (options.isGzip()) { 061 outputStream = new GZIPOutputStream(outputStream); 062 } 063 this.outputStream = new BufferedOutputStream(outputStream); 064 PackingUtils.config(options); 065 } 066 067 /** 068 * Creates an Archive with the given input file and a stream for the output 069 * 070 * @param jarFile - the input file 071 * @param outputStream TODO 072 * @param options - packing options (if null then defaults are used) 073 * @throws IOException If an I/O error occurs. 074 */ 075 public Archive(final JarFile jarFile, OutputStream outputStream, PackingOptions options) throws IOException { 076 if (options == null) { // use all defaults 077 options = new PackingOptions(); 078 } 079 this.options = options; 080 if (options.isGzip()) { 081 outputStream = new GZIPOutputStream(outputStream); 082 } 083 this.outputStream = new BufferedOutputStream(outputStream); 084 this.jarFile = jarFile; 085 jarInputStream = null; 086 PackingUtils.config(options); 087 } 088 089 /** 090 * Pack the archive 091 * 092 * @throws Pack200Exception TODO 093 * @throws IOException If an I/O error occurs. 094 */ 095 public void pack() throws Pack200Exception, IOException { 096 if (0 == options.getEffort()) { 097 doZeroEffortPack(); 098 } else { 099 doNormalPack(); 100 } 101 } 102 103 private void doZeroEffortPack() throws IOException, Pack200Exception { 104 PackingUtils.log("Start to perform a zero-effort packing"); 105 if (jarInputStream != null) { 106 PackingUtils.copyThroughJar(jarInputStream, outputStream); 107 } else { 108 PackingUtils.copyThroughJar(jarFile, outputStream); 109 } 110 } 111 112 private void doNormalPack() throws IOException, Pack200Exception { 113 PackingUtils.log("Start to perform a normal packing"); 114 List packingFileList; 115 if (jarInputStream != null) { 116 packingFileList = PackingUtils.getPackingFileListFromJar(jarInputStream, options.isKeepFileOrder()); 117 } else { 118 packingFileList = PackingUtils.getPackingFileListFromJar(jarFile, options.isKeepFileOrder()); 119 } 120 121 final List segmentUnitList = splitIntoSegments(packingFileList); 122 int previousByteAmount = 0; 123 int packedByteAmount = 0; 124 125 final int segmentSize = segmentUnitList.size(); 126 SegmentUnit segmentUnit; 127 for (int index = 0; index < segmentSize; index++) { 128 segmentUnit = (SegmentUnit) segmentUnitList.get(index); 129 new Segment().pack(segmentUnit, outputStream, options); 130 previousByteAmount += segmentUnit.getByteAmount(); 131 packedByteAmount += segmentUnit.getPackedByteAmount(); 132 } 133 134 PackingUtils.log("Total: Packed " + previousByteAmount + " input bytes of " + packingFileList.size() 135 + " files into " + packedByteAmount + " bytes in " + segmentSize + " segments"); 136 137 outputStream.close(); 138 } 139 140 private List splitIntoSegments(final List packingFileList) throws IOException, Pack200Exception { 141 final List segmentUnitList = new ArrayList(); 142 List classes = new ArrayList(); 143 List files = new ArrayList(); 144 final long segmentLimit = options.getSegmentLimit(); 145 146 final int size = packingFileList.size(); 147 PackingFile packingFile; 148 for (int index = 0; index < size; index++) { 149 packingFile = (PackingFile) packingFileList.get(index); 150 if (!addJarEntry(packingFile, classes, files)) { 151 // not added because segment has reached maximum size 152 segmentUnitList.add(new SegmentUnit(classes, files)); 153 classes = new ArrayList(); 154 files = new ArrayList(); 155 currentSegmentSize = 0; 156 // add the jar to a new segment 157 addJarEntry(packingFile, classes, files); 158 // ignore the size of first entry for compatibility with RI 159 currentSegmentSize = 0; 160 } else if (segmentLimit == 0 && estimateSize(packingFile) > 0) { 161 // create a new segment for each class unless size is 0 162 segmentUnitList.add(new SegmentUnit(classes, files)); 163 classes = new ArrayList(); 164 files = new ArrayList(); 165 } 166 } 167 // Change for Apache Commons Compress based on Apache Harmony. 168 // if (classes.size() > 0 && files.size() > 0) { 169 if (classes.size() > 0 || files.size() > 0) { 170 segmentUnitList.add(new SegmentUnit(classes, files)); 171 } 172 return segmentUnitList; 173 } 174 175 private boolean addJarEntry(final PackingFile packingFile, final List javaClasses, final List files) 176 throws IOException, Pack200Exception { 177 final long segmentLimit = options.getSegmentLimit(); 178 if (segmentLimit != -1 && segmentLimit != 0) { 179 // -1 is a special case where only one segment is created and 180 // 0 is a special case where one segment is created for each file 181 // except for files in "META-INF" 182 final long packedSize = estimateSize(packingFile); 183 if (packedSize + currentSegmentSize > segmentLimit && currentSegmentSize > 0) { 184 // don't add this JarEntry to the current segment 185 return false; 186 } 187 // do add this JarEntry 188 currentSegmentSize += packedSize; 189 } 190 191 final String name = packingFile.getName(); 192 if (name.endsWith(".class") && !options.isPassFile(name)) { 193 final Pack200ClassReader classParser = new Pack200ClassReader(packingFile.contents); 194 classParser.setFileName(name); 195 javaClasses.add(classParser); 196 packingFile.contents = new byte[0]; 197 } 198 files.add(packingFile); 199 return true; 200 } 201 202 private long estimateSize(final PackingFile packingFile) { 203 // The heuristic used here is for compatibility with the RI and should 204 // not be changed 205 final String name = packingFile.getName(); 206 if (name.startsWith("META-INF") || name.startsWith("/META-INF")) { 207 return 0; 208 } 209 long fileSize = packingFile.contents.length; 210 if (fileSize < 0) { 211 fileSize = 0; 212 } 213 return name.length() + fileSize + 5; 214 } 215 216 static class SegmentUnit { 217 218 private final List classList; 219 220 private final List fileList; 221 222 private int byteAmount = 0; 223 224 private int packedByteAmount = 0; 225 226 public SegmentUnit(final List classes, final List files) { 227 classList = classes; 228 fileList = files; 229 230 // Calculate the amount of bytes in classes and files before packing 231 Pack200ClassReader classReader; 232 for (final Iterator iterator = classList.iterator(); iterator.hasNext();) { 233 classReader = (Pack200ClassReader) iterator.next(); 234 byteAmount += classReader.b.length; 235 } 236 237 PackingFile file; 238 for (final Iterator iterator = fileList.iterator(); iterator.hasNext();) { 239 file = (PackingFile) iterator.next(); 240 byteAmount += file.contents.length; 241 } 242 } 243 244 public List getClassList() { 245 return classList; 246 } 247 248 public int classListSize() { 249 return classList.size(); 250 } 251 252 public int fileListSize() { 253 return fileList.size(); 254 } 255 256 public List getFileList() { 257 return fileList; 258 } 259 260 public int getByteAmount() { 261 return byteAmount; 262 } 263 264 public int getPackedByteAmount() { 265 return packedByteAmount; 266 } 267 268 public void addPackedByteAmount(final int amount) { 269 packedByteAmount += amount; 270 } 271 } 272 273 static class PackingFile { 274 275 private final String name; 276 private byte[] contents; 277 private final long modtime; 278 private final boolean deflateHint; 279 private final boolean isDirectory; 280 281 public PackingFile(final String name, final byte[] contents, final long modtime) { 282 this.name = name; 283 this.contents = contents; 284 this.modtime = modtime; 285 deflateHint = false; 286 isDirectory = false; 287 } 288 289 public PackingFile(final byte[] bytes, final JarEntry jarEntry) { 290 name = jarEntry.getName(); 291 contents = bytes; 292 modtime = jarEntry.getTime(); 293 deflateHint = jarEntry.getMethod() == ZipEntry.DEFLATED; 294 isDirectory = jarEntry.isDirectory(); 295 } 296 297 public byte[] getContents() { 298 return contents; 299 } 300 301 public String getName() { 302 return name; 303 } 304 305 public long getModtime() { 306 return modtime; 307 } 308 309 public void setContents(final byte[] contents) { 310 this.contents = contents; 311 } 312 313 public boolean isDefalteHint() { 314 return deflateHint; 315 } 316 317 public boolean isDirectory() { 318 return isDirectory; 319 } 320 321 @Override 322 public String toString() { 323 return name; 324 } 325 } 326 327}