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