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.unpack200; 018 019import java.io.BufferedInputStream; 020import java.io.ByteArrayInputStream; 021import java.io.ByteArrayOutputStream; 022import java.io.DataOutputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026import java.io.PrintWriter; 027import java.util.ArrayList; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Set; 031import java.util.TimeZone; 032import java.util.jar.JarEntry; 033import java.util.jar.JarOutputStream; 034import java.util.zip.CRC32; 035import java.util.zip.GZIPInputStream; 036import java.util.zip.ZipEntry; 037 038import org.apache.commons.compress.harmony.pack200.Codec; 039import org.apache.commons.compress.harmony.pack200.Pack200Exception; 040import org.apache.commons.compress.harmony.unpack200.bytecode.Attribute; 041import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass; 042import org.apache.commons.compress.harmony.unpack200.bytecode.CPField; 043import org.apache.commons.compress.harmony.unpack200.bytecode.CPMethod; 044import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8; 045import org.apache.commons.compress.harmony.unpack200.bytecode.ClassConstantPool; 046import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFile; 047import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFileEntry; 048import org.apache.commons.compress.harmony.unpack200.bytecode.InnerClassesAttribute; 049import org.apache.commons.compress.harmony.unpack200.bytecode.SourceFileAttribute; 050 051/** 052 * A Pack200 archive consists of one or more segments. Each segment is stand-alone, in the sense that every segment has 053 * the magic number header; thus, every segment is also a valid archive. However, it is possible to combine 054 * (non-GZipped) archives into a single large archive by concatenation alone. Thus all the hard work in unpacking an 055 * archive falls to understanding a segment. 056 * 057 * The first component of a segment is the header; this contains (amongst other things) the expected counts of constant 058 * pool entries, which in turn defines how many values need to be read from the stream. Because values are variable 059 * width (see {@link Codec}), it is not possible to calculate the start of the next segment, although one of the header 060 * values does hint at the size of the segment if non-zero, which can be used for buffering purposes. 061 * 062 * Note that this does not perform any buffering of the input stream; each value will be read on a byte-by-byte basis. 063 * It does not perform GZip decompression automatically; both of these are expected to be done by the caller if the 064 * stream has the magic header for GZip streams ({@link GZIPInputStream#GZIP_MAGIC}). In any case, if GZip decompression 065 * is being performed the input stream will be buffered at a higher level, and thus this can read on a byte-oriented 066 * basis. 067 */ 068public class Segment { 069 070 public static final int LOG_LEVEL_VERBOSE = 2; 071 072 public static final int LOG_LEVEL_STANDARD = 1; 073 074 public static final int LOG_LEVEL_QUIET = 0; 075 076 private SegmentHeader header; 077 078 private CpBands cpBands; 079 080 private AttrDefinitionBands attrDefinitionBands; 081 082 private IcBands icBands; 083 084 private ClassBands classBands; 085 086 private BcBands bcBands; 087 088 private FileBands fileBands; 089 090 private boolean overrideDeflateHint; 091 092 private boolean deflateHint; 093 094 private boolean doPreRead; 095 096 private int logLevel; 097 098 private PrintWriter logStream; 099 100 private byte[][] classFilesContents; 101 102 private boolean[] fileDeflate; 103 104 private boolean[] fileIsClass; 105 106 private InputStream internalBuffer; 107 108 private ClassFile buildClassFile(final int classNum) throws Pack200Exception { 109 final ClassFile classFile = new ClassFile(); 110 final int[] major = classBands.getClassVersionMajor(); 111 final int[] minor = classBands.getClassVersionMinor(); 112 if (major != null) { 113 classFile.major = major[classNum]; 114 classFile.minor = minor[classNum]; 115 } else { 116 classFile.major = header.getDefaultClassMajorVersion(); 117 classFile.minor = header.getDefaultClassMinorVersion(); 118 } 119 // build constant pool 120 final ClassConstantPool cp = classFile.pool; 121 final int fullNameIndexInCpClass = classBands.getClassThisInts()[classNum]; 122 final String fullName = cpBands.getCpClass()[fullNameIndexInCpClass]; 123 // SourceFile attribute 124 int i = fullName.lastIndexOf("/") + 1; // if lastIndexOf==-1, then 125 // -1+1=0, so str.substring(0) 126 // == str 127 128 // Get the source file attribute 129 final ArrayList classAttributes = classBands.getClassAttributes()[classNum]; 130 SourceFileAttribute sourceFileAttribute = null; 131 for (int index = 0; index < classAttributes.size(); index++) { 132 if (((Attribute) classAttributes.get(index)).isSourceFileAttribute()) { 133 sourceFileAttribute = ((SourceFileAttribute) classAttributes.get(index)); 134 } 135 } 136 137 if (sourceFileAttribute == null) { 138 // If we don't have a source file attribute yet, we need 139 // to infer it from the class. 140 final AttributeLayout SOURCE_FILE = attrDefinitionBands.getAttributeDefinitionMap() 141 .getAttributeLayout(AttributeLayout.ATTRIBUTE_SOURCE_FILE, AttributeLayout.CONTEXT_CLASS); 142 if (SOURCE_FILE.matches(classBands.getRawClassFlags()[classNum])) { 143 int firstDollar = -1; 144 for (int index = 0; index < fullName.length(); index++) { 145 if (fullName.charAt(index) <= '$') { 146 firstDollar = index; 147 } 148 } 149 String fileName = null; 150 151 if (firstDollar > -1 && (i <= firstDollar)) { 152 fileName = fullName.substring(i, firstDollar) + ".java"; 153 } else { 154 fileName = fullName.substring(i) + ".java"; 155 } 156 sourceFileAttribute = new SourceFileAttribute(cpBands.cpUTF8Value(fileName, false)); 157 classFile.attributes = new Attribute[] {(Attribute) cp.add(sourceFileAttribute)}; 158 } else { 159 classFile.attributes = new Attribute[] {}; 160 } 161 } else { 162 classFile.attributes = new Attribute[] {(Attribute) cp.add(sourceFileAttribute)}; 163 } 164 165 // If we see any class attributes, add them to the class's attributes 166 // that will 167 // be written out. Keep SourceFileAttributes out since we just 168 // did them above. 169 final ArrayList classAttributesWithoutSourceFileAttribute = new ArrayList(classAttributes.size()); 170 for (int index = 0; index < classAttributes.size(); index++) { 171 final Attribute attrib = (Attribute) classAttributes.get(index); 172 if (!attrib.isSourceFileAttribute()) { 173 classAttributesWithoutSourceFileAttribute.add(attrib); 174 } 175 } 176 final Attribute[] originalAttributes = classFile.attributes; 177 classFile.attributes = new Attribute[originalAttributes.length 178 + classAttributesWithoutSourceFileAttribute.size()]; 179 System.arraycopy(originalAttributes, 0, classFile.attributes, 0, originalAttributes.length); 180 for (int index = 0; index < classAttributesWithoutSourceFileAttribute.size(); index++) { 181 final Attribute attrib = ((Attribute) classAttributesWithoutSourceFileAttribute.get(index)); 182 cp.add(attrib); 183 classFile.attributes[originalAttributes.length + index] = attrib; 184 } 185 186 // this/superclass 187 final ClassFileEntry cfThis = cp.add(cpBands.cpClassValue(fullNameIndexInCpClass)); 188 final ClassFileEntry cfSuper = cp.add(cpBands.cpClassValue(classBands.getClassSuperInts()[classNum])); 189 // add interfaces 190 final ClassFileEntry cfInterfaces[] = new ClassFileEntry[classBands.getClassInterfacesInts()[classNum].length]; 191 for (i = 0; i < cfInterfaces.length; i++) { 192 cfInterfaces[i] = cp.add(cpBands.cpClassValue(classBands.getClassInterfacesInts()[classNum][i])); 193 } 194 // add fields 195 final ClassFileEntry cfFields[] = new ClassFileEntry[classBands.getClassFieldCount()[classNum]]; 196 // fieldDescr and fieldFlags used to create this 197 for (i = 0; i < cfFields.length; i++) { 198 final int descriptorIndex = classBands.getFieldDescrInts()[classNum][i]; 199 final int nameIndex = cpBands.getCpDescriptorNameInts()[descriptorIndex]; 200 final int typeIndex = cpBands.getCpDescriptorTypeInts()[descriptorIndex]; 201 final CPUTF8 name = cpBands.cpUTF8Value(nameIndex); 202 final CPUTF8 descriptor = cpBands.cpSignatureValue(typeIndex); 203 cfFields[i] = cp.add(new CPField(name, descriptor, classBands.getFieldFlags()[classNum][i], 204 classBands.getFieldAttributes()[classNum][i])); 205 } 206 // add methods 207 final ClassFileEntry cfMethods[] = new ClassFileEntry[classBands.getClassMethodCount()[classNum]]; 208 // methodDescr and methodFlags used to create this 209 for (i = 0; i < cfMethods.length; i++) { 210 final int descriptorIndex = classBands.getMethodDescrInts()[classNum][i]; 211 final int nameIndex = cpBands.getCpDescriptorNameInts()[descriptorIndex]; 212 final int typeIndex = cpBands.getCpDescriptorTypeInts()[descriptorIndex]; 213 final CPUTF8 name = cpBands.cpUTF8Value(nameIndex); 214 final CPUTF8 descriptor = cpBands.cpSignatureValue(typeIndex); 215 cfMethods[i] = cp.add(new CPMethod(name, descriptor, classBands.getMethodFlags()[classNum][i], 216 classBands.getMethodAttributes()[classNum][i])); 217 } 218 cp.addNestedEntries(); 219 220 // add inner class attribute (if required) 221 boolean addInnerClassesAttr = false; 222 final IcTuple[] ic_local = getClassBands().getIcLocal()[classNum]; 223 final boolean ic_local_sent = ic_local != null; 224 final InnerClassesAttribute innerClassesAttribute = new InnerClassesAttribute("InnerClasses"); 225 final IcTuple[] ic_relevant = getIcBands().getRelevantIcTuples(fullName, cp); 226 final List ic_stored = computeIcStored(ic_local, ic_relevant); 227 for (int index = 0; index < ic_stored.size(); index++) { 228 final IcTuple icStored = (IcTuple) ic_stored.get(index); 229 final int innerClassIndex = icStored.thisClassIndex(); 230 final int outerClassIndex = icStored.outerClassIndex(); 231 final int simpleClassNameIndex = icStored.simpleClassNameIndex(); 232 233 final String innerClassString = icStored.thisClassString(); 234 final String outerClassString = icStored.outerClassString(); 235 final String simpleClassName = icStored.simpleClassName(); 236 237 CPClass innerClass = null; 238 CPUTF8 innerName = null; 239 CPClass outerClass = null; 240 241 innerClass = innerClassIndex != -1 ? cpBands.cpClassValue(innerClassIndex) 242 : cpBands.cpClassValue(innerClassString); 243 if (!icStored.isAnonymous()) { 244 innerName = simpleClassNameIndex != -1 ? cpBands.cpUTF8Value(simpleClassNameIndex) 245 : cpBands.cpUTF8Value(simpleClassName); 246 } 247 248 if (icStored.isMember()) { 249 outerClass = outerClassIndex != -1 ? cpBands.cpClassValue(outerClassIndex) 250 : cpBands.cpClassValue(outerClassString); 251 } 252 final int flags = icStored.F; 253 innerClassesAttribute.addInnerClassesEntry(innerClass, outerClass, innerName, flags); 254 addInnerClassesAttr = true; 255 } 256 // If ic_local is sent and it's empty, don't add 257 // the inner classes attribute. 258 if (ic_local_sent && (ic_local.length == 0)) { 259 addInnerClassesAttr = false; 260 } 261 262 // If ic_local is not sent and ic_relevant is empty, 263 // don't add the inner class attribute. 264 if (!ic_local_sent && (ic_relevant.length == 0)) { 265 addInnerClassesAttr = false; 266 } 267 268 if (addInnerClassesAttr) { 269 // Need to add the InnerClasses attribute to the 270 // existing classFile attributes. 271 final Attribute[] originalAttrs = classFile.attributes; 272 final Attribute[] newAttrs = new Attribute[originalAttrs.length + 1]; 273 for (int index = 0; index < originalAttrs.length; index++) { 274 newAttrs[index] = originalAttrs[index]; 275 } 276 newAttrs[newAttrs.length - 1] = innerClassesAttribute; 277 classFile.attributes = newAttrs; 278 cp.addWithNestedEntries(innerClassesAttribute); 279 } 280 // sort CP according to cp_All 281 cp.resolve(this); 282 // NOTE the indexOf is only valid after the cp.resolve() 283 // build up remainder of file 284 classFile.accessFlags = (int) classBands.getClassFlags()[classNum]; 285 classFile.thisClass = cp.indexOf(cfThis); 286 classFile.superClass = cp.indexOf(cfSuper); 287 // TODO placate format of file for writing purposes 288 classFile.interfaces = new int[cfInterfaces.length]; 289 for (i = 0; i < cfInterfaces.length; i++) { 290 classFile.interfaces[i] = cp.indexOf(cfInterfaces[i]); 291 } 292 classFile.fields = cfFields; 293 classFile.methods = cfMethods; 294 return classFile; 295 } 296 297 /** 298 * Given an ic_local and an ic_relevant, use them to calculate what should be added as ic_stored. 299 * 300 * @param ic_local IcTuple[] array of local transmitted tuples 301 * @param ic_relevant IcTuple[] array of relevant tuples 302 * @return List of tuples to be stored. If ic_local is null or empty, the values returned may not be correct. The 303 * caller will have to determine if this is the case. 304 */ 305 private List computeIcStored(final IcTuple[] ic_local, final IcTuple[] ic_relevant) { 306 final List result = new ArrayList(ic_relevant.length); 307 final List duplicates = new ArrayList(ic_relevant.length); 308 final Set isInResult = new HashSet(ic_relevant.length); 309 310 // need to compute: 311 // result = ic_local XOR ic_relevant 312 313 // add ic_local 314 if (ic_local != null) { 315 for (int index = 0; index < ic_local.length; index++) { 316 if (isInResult.add(ic_local[index])) { 317 result.add(ic_local[index]); 318 } 319 } 320 } 321 322 // add ic_relevant 323 for (int index = 0; index < ic_relevant.length; index++) { 324 if (isInResult.add(ic_relevant[index])) { 325 result.add(ic_relevant[index]); 326 } else { 327 duplicates.add(ic_relevant[index]); 328 } 329 } 330 331 // eliminate "duplicates" 332 for (int index = 0; index < duplicates.size(); index++) { 333 final IcTuple tuple = (IcTuple) duplicates.get(index); 334 result.remove(tuple); 335 } 336 337 return result; 338 } 339 340 /** 341 * This performs reading the data from the stream into non-static instance of Segment. After the completion of this 342 * method stream can be freed. 343 * 344 * @param in the input stream to read from 345 * @throws IOException if a problem occurs during reading from the underlying stream 346 * @throws Pack200Exception if a problem occurs with an unexpected value or unsupported codec 347 */ 348 private void readSegment(final InputStream in) throws IOException, Pack200Exception { 349 log(LOG_LEVEL_VERBOSE, "-------"); 350 cpBands = new CpBands(this); 351 cpBands.read(in); 352 attrDefinitionBands = new AttrDefinitionBands(this); 353 attrDefinitionBands.read(in); 354 icBands = new IcBands(this); 355 icBands.read(in); 356 classBands = new ClassBands(this); 357 classBands.read(in); 358 bcBands = new BcBands(this); 359 bcBands.read(in); 360 fileBands = new FileBands(this); 361 fileBands.read(in); 362 363 fileBands.processFileBits(); 364 } 365 366 /** 367 * This performs the actual work of parsing against a non-static instance of Segment. This method is intended to run 368 * concurrently for multiple segments. 369 * 370 * @throws IOException if a problem occurs during reading from the underlying stream 371 * @throws Pack200Exception if a problem occurs with an unexpected value or unsupported codec 372 */ 373 private void parseSegment() throws IOException, Pack200Exception { 374 375 header.unpack(); 376 cpBands.unpack(); 377 attrDefinitionBands.unpack(); 378 icBands.unpack(); 379 classBands.unpack(); 380 bcBands.unpack(); 381 fileBands.unpack(); 382 383 int classNum = 0; 384 final int numberOfFiles = header.getNumberOfFiles(); 385 final String[] fileName = fileBands.getFileName(); 386 final int[] fileOptions = fileBands.getFileOptions(); 387 final SegmentOptions options = header.getOptions(); 388 389 classFilesContents = new byte[numberOfFiles][]; 390 fileDeflate = new boolean[numberOfFiles]; 391 fileIsClass = new boolean[numberOfFiles]; 392 393 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 394 final DataOutputStream dos = new DataOutputStream(bos); 395 396 for (int i = 0; i < numberOfFiles; i++) { 397 String name = fileName[i]; 398 399 final boolean nameIsEmpty = (name == null) || name.equals(""); 400 final boolean isClass = (fileOptions[i] & 2) == 2 || nameIsEmpty; 401 if (isClass && nameIsEmpty) { 402 name = cpBands.getCpClass()[classBands.getClassThisInts()[classNum]] + ".class"; 403 fileName[i] = name; 404 } 405 406 if (!overrideDeflateHint) { 407 fileDeflate[i] = (fileOptions[i] & 1) == 1 || options.shouldDeflate(); 408 } else { 409 fileDeflate[i] = deflateHint; 410 } 411 412 fileIsClass[i] = isClass; 413 414 if (isClass) { 415 final ClassFile classFile = buildClassFile(classNum); 416 classFile.write(dos); 417 dos.flush(); 418 419 classFilesContents[classNum] = bos.toByteArray(); 420 bos.reset(); 421 422 classNum++; 423 } 424 } 425 } 426 427 /** 428 * Unpacks a packed stream (either .pack. or .pack.gz) into a corresponding JarOuputStream. 429 * 430 * @param in a packed stream. 431 * @param out output stream. 432 * @throws Pack200Exception if there is a problem unpacking 433 * @throws IOException if there is a problem with I/O during unpacking 434 */ 435 public void unpack(final InputStream in, final JarOutputStream out) throws IOException, Pack200Exception { 436 unpackRead(in); 437 unpackProcess(); 438 unpackWrite(out); 439 } 440 441 /* 442 * Package-private accessors for unpacking stages 443 */ 444 void unpackRead(InputStream in) throws IOException, Pack200Exception { 445 if (!in.markSupported()) { 446 in = new BufferedInputStream(in); 447 } 448 449 header = new SegmentHeader(this); 450 header.read(in); 451 452 final int size = (int) header.getArchiveSize() - header.getArchiveSizeOffset(); 453 454 if (doPreRead && header.getArchiveSize() != 0) { 455 final byte[] data = new byte[size]; 456 in.read(data); 457 internalBuffer = new BufferedInputStream(new ByteArrayInputStream(data)); 458 } else { 459 readSegment(in); 460 } 461 } 462 463 void unpackProcess() throws IOException, Pack200Exception { 464 if (internalBuffer != null) { 465 readSegment(internalBuffer); 466 } 467 parseSegment(); 468 } 469 470 void unpackWrite(final JarOutputStream out) throws IOException, Pack200Exception { 471 writeJar(out); 472 if (logStream != null) { 473 logStream.close(); 474 } 475 } 476 477 /** 478 * Writes the segment to an output stream. The output stream should be pre-buffered for efficiency. Also takes the 479 * same input stream for reading, since the file bits may not be loaded and thus just copied from one stream to 480 * another. Doesn't close the output stream when finished, in case there are more entries (e.g. further segments) to 481 * be written. 482 * 483 * @param out the JarOutputStream to write data to 484 * @throws IOException if an error occurs while reading or writing to the streams 485 * @throws Pack200Exception if an error occurs while processing data 486 */ 487 public void writeJar(final JarOutputStream out) throws IOException, Pack200Exception { 488 final String[] fileName = fileBands.getFileName(); 489 final int[] fileModtime = fileBands.getFileModtime(); 490 final long[] fileSize = fileBands.getFileSize(); 491 final byte[][] fileBits = fileBands.getFileBits(); 492 493 // now write the files out 494 int classNum = 0; 495 final int numberOfFiles = header.getNumberOfFiles(); 496 final long archiveModtime = header.getArchiveModtime(); 497 498 for (int i = 0; i < numberOfFiles; i++) { 499 final String name = fileName[i]; 500 // For Pack200 archives, modtime is in seconds 501 // from the epoch. JarEntries need it to be in 502 // milliseconds from the epoch. 503 // Even though we're adding two longs and multiplying 504 // by 1000, we won't overflow because both longs are 505 // always under 2^32. 506 final long modtime = 1000 * (archiveModtime + fileModtime[i]); 507 final boolean deflate = fileDeflate[i]; 508 509 final JarEntry entry = new JarEntry(name); 510 if (deflate) { 511 entry.setMethod(ZipEntry.DEFLATED); 512 } else { 513 entry.setMethod(ZipEntry.STORED); 514 final CRC32 crc = new CRC32(); 515 if (fileIsClass[i]) { 516 crc.update(classFilesContents[classNum]); 517 entry.setSize(classFilesContents[classNum].length); 518 } else { 519 crc.update(fileBits[i]); 520 entry.setSize(fileSize[i]); 521 } 522 entry.setCrc(crc.getValue()); 523 } 524 // On Windows at least, need to correct for timezone 525 entry.setTime(modtime - TimeZone.getDefault().getRawOffset()); 526 out.putNextEntry(entry); 527 528 // write to output stream 529 if (fileIsClass[i]) { 530 entry.setSize(classFilesContents[classNum].length); 531 out.write(classFilesContents[classNum]); 532 classNum++; 533 } else { 534 entry.setSize(fileSize[i]); 535 out.write(fileBits[i]); 536 } 537 } 538 } 539 540 public SegmentConstantPool getConstantPool() { 541 return cpBands.getConstantPool(); 542 } 543 544 public SegmentHeader getSegmentHeader() { 545 return header; 546 } 547 548 public void setPreRead(final boolean value) { 549 doPreRead = value; 550 } 551 552 protected AttrDefinitionBands getAttrDefinitionBands() { 553 return attrDefinitionBands; 554 } 555 556 protected ClassBands getClassBands() { 557 return classBands; 558 } 559 560 protected CpBands getCpBands() { 561 return cpBands; 562 } 563 564 protected IcBands getIcBands() { 565 return icBands; 566 } 567 568 public void setLogLevel(final int logLevel) { 569 this.logLevel = logLevel; 570 } 571 572 public void setLogStream(final OutputStream logStream) { 573 this.logStream = new PrintWriter(logStream); 574 } 575 576 public void log(final int logLevel, final String message) { 577 if (this.logLevel >= logLevel) { 578 logStream.println(message); 579 } 580 } 581 582 /** 583 * Override the archive's deflate hint with the given boolean 584 * 585 * @param deflateHint - the deflate hint to use 586 */ 587 public void overrideDeflateHint(final boolean deflateHint) { 588 this.overrideDeflateHint = true; 589 this.deflateHint = deflateHint; 590 } 591 592}