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.ByteArrayInputStream; 020import java.io.EOFException; 021import java.io.IOException; 022import java.io.InputStream; 023 024import org.apache.commons.compress.harmony.pack200.BHSDCodec; 025import org.apache.commons.compress.harmony.pack200.Codec; 026import org.apache.commons.compress.harmony.pack200.Pack200Exception; 027 028/** 029 * SegmentHeader is the header band of a {@link Segment} 030 */ 031public class SegmentHeader { 032 033 /** 034 * The magic header for a Pack200 Segment is 0xCAFED00D. I wonder where they get their inspiration from ... 035 */ 036 private static final int[] magic = {0xCA, 0xFE, 0xD0, 0x0D}; 037 038 private int archiveMajor; 039 040 private int archiveMinor; 041 042 private long archiveModtime; 043 044 private long archiveSize; 045 046 private int attributeDefinitionCount; 047 048 private InputStream bandHeadersInputStream; 049 050 private int bandHeadersSize; 051 052 private int classCount; 053 054 private int cpClassCount; 055 056 private int cpDescriptorCount; 057 058 private int cpDoubleCount; 059 060 private int cpFieldCount; 061 062 private int cpFloatCount; 063 064 private int cpIMethodCount; 065 066 private int cpIntCount; 067 068 private int cpLongCount; 069 070 private int cpMethodCount; 071 072 private int cpSignatureCount; 073 074 private int cpStringCount; 075 076 private int cpUTF8Count; 077 078 private int defaultClassMajorVersion; 079 080 private int defaultClassMinorVersion; 081 082 private int innerClassCount; 083 084 private int numberOfFiles; 085 086 private int segmentsRemaining; 087 088 private SegmentOptions options; 089 090 private final Segment segment; 091 092 private int archiveSizeOffset; 093 094 public SegmentHeader(final Segment segment) { 095 this.segment = segment; 096 } 097 098 /** 099 * Decode a scalar from the band file. A scalar is like a band, but does not perform any band code switching. 100 * 101 * @param name the name of the scalar (primarily for logging/debugging purposes) 102 * @param in the input stream to read from 103 * @param codec the codec for this scalar 104 * @return the decoded value 105 * @throws IOException if there is a problem reading from the underlying input stream 106 * @throws Pack200Exception if there is a problem decoding the value or that the value is invalid 107 */ 108 private int decodeScalar(final String name, final InputStream in, final BHSDCodec codec) 109 throws IOException, Pack200Exception { 110 final int ret = codec.decode(in); 111 segment.log(Segment.LOG_LEVEL_VERBOSE, "Parsed #" + name + " as " + ret); 112 return ret; 113 } 114 115 /** 116 * Decode a number of scalars from the band file. A scalar is like a band, but does not perform any band code 117 * switching. 118 * 119 * @param name the name of the scalar (primarily for logging/debugging purposes) 120 * @param in the input stream to read from 121 * @param codec the codec for this scalar 122 * @return an array of decoded {@code long[]} values 123 * @throws IOException if there is a problem reading from the underlying input stream 124 * @throws Pack200Exception if there is a problem decoding the value or that the value is invalid 125 */ 126 private int[] decodeScalar(final String name, final InputStream in, final BHSDCodec codec, final int n) 127 throws IOException, Pack200Exception { 128 segment.log(Segment.LOG_LEVEL_VERBOSE, "Parsed #" + name + " (" + n + ")"); 129 return codec.decodeInts(n, in); 130 } 131 132 public long getArchiveModtime() { 133 return archiveModtime; 134 } 135 136 public long getArchiveSize() { 137 return archiveSize; 138 } 139 140 public int getArchiveSizeOffset() { 141 return archiveSizeOffset; 142 } 143 144 public int getAttributeDefinitionCount() { 145 return attributeDefinitionCount; 146 } 147 148 /** 149 * Obtain the band headers data as an input stream. If no band headers are present, this will return an empty input 150 * stream to prevent any further reads taking place. 151 * 152 * Note that as a stream, data consumed from this input stream can't be re-used. Data is only read from this stream 153 * if the encoding is such that additional information needs to be decoded from the stream itself. 154 * 155 * @return the band headers input stream 156 */ 157 public InputStream getBandHeadersInputStream() { 158 if (bandHeadersInputStream == null) { 159 bandHeadersInputStream = new ByteArrayInputStream(new byte[0]); 160 } 161 return bandHeadersInputStream; 162 163 } 164 165 public int getBandHeadersSize() { 166 return bandHeadersSize; 167 } 168 169 public int getClassCount() { 170 return classCount; 171 } 172 173 public int getCpClassCount() { 174 return cpClassCount; 175 } 176 177 public int getCpDescriptorCount() { 178 return cpDescriptorCount; 179 } 180 181 public int getCpDoubleCount() { 182 return cpDoubleCount; 183 } 184 185 public int getCpFieldCount() { 186 return cpFieldCount; 187 } 188 189 public int getCpFloatCount() { 190 return cpFloatCount; 191 } 192 193 public int getCpIMethodCount() { 194 return cpIMethodCount; 195 } 196 197 public int getCpIntCount() { 198 return cpIntCount; 199 } 200 201 public int getCpLongCount() { 202 return cpLongCount; 203 } 204 205 public int getCpMethodCount() { 206 return cpMethodCount; 207 } 208 209 public int getCpSignatureCount() { 210 return cpSignatureCount; 211 } 212 213 public int getCpStringCount() { 214 return cpStringCount; 215 } 216 217 public int getCpUTF8Count() { 218 return cpUTF8Count; 219 } 220 221 public int getDefaultClassMajorVersion() { 222 return defaultClassMajorVersion; 223 } 224 225 public int getDefaultClassMinorVersion() { 226 return defaultClassMinorVersion; 227 } 228 229 public int getInnerClassCount() { 230 return innerClassCount; 231 } 232 233 public int getNumberOfFiles() { 234 return numberOfFiles; 235 } 236 237 public SegmentOptions getOptions() { 238 return options; 239 } 240 241 public int getSegmentsRemaining() { 242 return segmentsRemaining; 243 } 244 245 private void parseArchiveFileCounts(final InputStream in) throws IOException, Pack200Exception { 246 if (options.hasArchiveFileCounts()) { 247 setArchiveSize((long) decodeScalar("archive_size_hi", in, Codec.UNSIGNED5) << 32 | 248 decodeScalar("archive_size_lo", in, Codec.UNSIGNED5)); 249 archiveSizeOffset = in.available(); 250 setSegmentsRemaining(decodeScalar("archive_next_count", in, Codec.UNSIGNED5)); 251 setArchiveModtime(decodeScalar("archive_modtime", in, Codec.UNSIGNED5)); 252 numberOfFiles = decodeScalar("file_count", in, Codec.UNSIGNED5); 253 } 254 } 255 256 private void parseArchiveSpecialCounts(final InputStream in) throws IOException, Pack200Exception { 257 if (getOptions().hasSpecialFormats()) { 258 bandHeadersSize = decodeScalar("band_headers_size", in, Codec.UNSIGNED5); 259 setAttributeDefinitionCount(decodeScalar("attr_definition_count", in, Codec.UNSIGNED5)); 260 } 261 } 262 263 private void parseClassCounts(final InputStream in) throws IOException, Pack200Exception { 264 innerClassCount = decodeScalar("ic_count", in, Codec.UNSIGNED5); 265 defaultClassMinorVersion = decodeScalar("default_class_minver", in, Codec.UNSIGNED5); 266 defaultClassMajorVersion = decodeScalar("default_class_majver", in, Codec.UNSIGNED5); 267 classCount = decodeScalar("class_count", in, Codec.UNSIGNED5); 268 } 269 270 private void parseCpCounts(final InputStream in) throws IOException, Pack200Exception { 271 cpUTF8Count = decodeScalar("cp_Utf8_count", in, Codec.UNSIGNED5); 272 if (getOptions().hasCPNumberCounts()) { 273 cpIntCount = decodeScalar("cp_Int_count", in, Codec.UNSIGNED5); 274 cpFloatCount = decodeScalar("cp_Float_count", in, Codec.UNSIGNED5); 275 cpLongCount = decodeScalar("cp_Long_count", in, Codec.UNSIGNED5); 276 cpDoubleCount = decodeScalar("cp_Double_count", in, Codec.UNSIGNED5); 277 } 278 cpStringCount = decodeScalar("cp_String_count", in, Codec.UNSIGNED5); 279 cpClassCount = decodeScalar("cp_Class_count", in, Codec.UNSIGNED5); 280 cpSignatureCount = decodeScalar("cp_Signature_count", in, Codec.UNSIGNED5); 281 cpDescriptorCount = decodeScalar("cp_Descr_count", in, Codec.UNSIGNED5); 282 cpFieldCount = decodeScalar("cp_Field_count", in, Codec.UNSIGNED5); 283 cpMethodCount = decodeScalar("cp_Method_count", in, Codec.UNSIGNED5); 284 cpIMethodCount = decodeScalar("cp_Imethod_count", in, Codec.UNSIGNED5); 285 } 286 287 public void read(final InputStream in) throws IOException, Error, Pack200Exception { 288 289 final int[] word = decodeScalar("archive_magic_word", in, Codec.BYTE1, magic.length); 290 for (int m = 0; m < magic.length; m++) { 291 if (word[m] != magic[m]) { 292 throw new Error("Bad header"); 293 } 294 } 295 setArchiveMinorVersion(decodeScalar("archive_minver", in, Codec.UNSIGNED5)); 296 setArchiveMajorVersion(decodeScalar("archive_majver", in, Codec.UNSIGNED5)); 297 options = new SegmentOptions(decodeScalar("archive_options", in, Codec.UNSIGNED5)); 298 parseArchiveFileCounts(in); 299 parseArchiveSpecialCounts(in); 300 parseCpCounts(in); 301 parseClassCounts(in); 302 303 if (getBandHeadersSize() > 0) { 304 final byte[] bandHeaders = new byte[getBandHeadersSize()]; 305 readFully(in, bandHeaders); 306 setBandHeadersData(bandHeaders); 307 } 308 309 archiveSizeOffset = archiveSizeOffset - in.available(); 310 } 311 312 /** 313 * Completely reads in a byte array, akin to the implementation in {@link java.lang.DataInputStream}. TODO Refactor 314 * out into a separate InputStream handling class 315 * 316 * @param in the input stream to read from 317 * @param data the byte array to read into 318 * @throws IOException if a problem occurs during reading from the underlying stream 319 */ 320 private void readFully(final InputStream in, final byte[] data) throws IOException { 321 int total = in.read(data); 322 if (total == -1) { 323 throw new EOFException("Failed to read any data from input stream"); 324 } 325 while (total < data.length) { 326 final int delta = in.read(data, total, data.length - total); 327 if (delta == -1) { 328 throw new EOFException("Failed to read some data from input stream"); 329 } 330 total += delta; 331 } 332 } 333 334 /** 335 * Sets the major version of this archive. 336 * 337 * @param version the minor version of the archive 338 * @throws Pack200Exception if the major version is not 150 339 */ 340 private void setArchiveMajorVersion(final int version) throws Pack200Exception { 341 if (version != 150) { 342 throw new Pack200Exception("Invalid segment major version: " + version); 343 } 344 archiveMajor = version; 345 } 346 347 /** 348 * Sets the minor version of this archive 349 * 350 * @param version the minor version of the archive 351 * @throws Pack200Exception if the minor version is not 7 352 */ 353 private void setArchiveMinorVersion(final int version) throws Pack200Exception { 354 if (version != 7) { 355 throw new Pack200Exception("Invalid segment minor version"); 356 } 357 archiveMinor = version; 358 } 359 360 public void setArchiveModtime(final long archiveModtime) { 361 this.archiveModtime = archiveModtime; 362 } 363 364 public void setArchiveSize(final long archiveSize) { 365 this.archiveSize = archiveSize; 366 } 367 368 private void setAttributeDefinitionCount(final long valuie) { 369 this.attributeDefinitionCount = (int) valuie; 370 } 371 372 private void setBandHeadersData(final byte[] bandHeaders) { 373 this.bandHeadersInputStream = new ByteArrayInputStream(bandHeaders); 374 } 375 376 public void setSegmentsRemaining(final long value) { 377 segmentsRemaining = (int) value; 378 } 379 380 public void unpack() { 381 382 } 383}