001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.commons.compress.harmony.pack200; 020 021import java.io.IOException; 022import java.io.OutputStream; 023import java.util.Arrays; 024import java.util.HashSet; 025import java.util.List; 026import java.util.Set; 027import java.util.TimeZone; 028 029import org.apache.commons.compress.harmony.pack200.Archive.PackingFile; 030import org.apache.commons.compress.harmony.pack200.Archive.SegmentUnit; 031import org.apache.commons.compress.utils.ExactMath; 032import org.objectweb.asm.ClassReader; 033 034/** 035 * Bands containing information about files in the pack200 archive and the file contents for non-class-files. Corresponds to the {@code file_bands} set of bands 036 * described in the specification. 037 */ 038public class FileBands extends BandSet { 039 040 private final CPUTF8[] fileName; 041 private int[] file_name; 042 private final int[] file_modtime; 043 private final long[] file_size; 044 private final int[] file_options; 045 private final byte[][] file_bits; 046 private final List<PackingFile> fileList; 047 private final PackingOptions options; 048 private final CpBands cpBands; 049 050 public FileBands(final CpBands cpBands, final SegmentHeader segmentHeader, final PackingOptions options, final SegmentUnit segmentUnit, final int effort) { 051 super(effort, segmentHeader); 052 fileList = segmentUnit.getFileList(); 053 this.options = options; 054 this.cpBands = cpBands; 055 final int size = fileList.size(); 056 fileName = new CPUTF8[size]; 057 file_modtime = new int[size]; 058 file_size = new long[size]; 059 file_options = new int[size]; 060 int totalSize = 0; 061 file_bits = new byte[size][]; 062 final int archiveModtime = segmentHeader.getArchive_modtime(); 063 064 final Set<String> classNames = new HashSet<>(); 065 for (final ClassReader reader : segmentUnit.getClassList()) { 066 classNames.add(reader.getClassName()); 067 } 068 final CPUTF8 emptyString = cpBands.getCPUtf8(""); 069 long modtime; 070 int latestModtime = Integer.MIN_VALUE; 071 final boolean isLatest = !PackingOptions.KEEP.equals(options.getModificationTime()); 072 for (int i = 0; i < size; i++) { 073 final PackingFile packingFile = fileList.get(i); 074 final String name = packingFile.getName(); 075 if (name.endsWith(".class") && !options.isPassFile(name)) { 076 file_options[i] |= 1 << 1; 077 if (classNames.contains(name.substring(0, name.length() - 6))) { 078 fileName[i] = emptyString; 079 } else { 080 fileName[i] = cpBands.getCPUtf8(name); 081 } 082 } else { 083 fileName[i] = cpBands.getCPUtf8(name); 084 } 085 // set deflate_hint for file element 086 if (options.isKeepDeflateHint() && packingFile.isDefalteHint()) { 087 file_options[i] |= 0x1; 088 } 089 final byte[] bytes = packingFile.getContents(); 090 file_size[i] = bytes.length; 091 totalSize = ExactMath.add(totalSize, file_size[i]); 092 093 // update modification time 094 modtime = (packingFile.getModtime() + TimeZone.getDefault().getRawOffset()) / 1000L; 095 file_modtime[i] = (int) (modtime - archiveModtime); 096 if (isLatest && latestModtime < file_modtime[i]) { 097 latestModtime = file_modtime[i]; 098 } 099 100 file_bits[i] = packingFile.getContents(); 101 } 102 103 if (isLatest) { 104 Arrays.fill(file_modtime, latestModtime); 105 } 106 } 107 108 /** 109 * All input classes for the segment have now been read in, so this method is called so that this class can calculate/complete anything it could not do 110 * while classes were being read. 111 */ 112 public void finaliseBands() { 113 file_name = new int[fileName.length]; 114 for (int i = 0; i < file_name.length; i++) { 115 if (fileName[i].equals(cpBands.getCPUtf8(""))) { 116 final PackingFile packingFile = fileList.get(i); 117 final String name = packingFile.getName(); 118 if (options.isPassFile(name)) { 119 fileName[i] = cpBands.getCPUtf8(name); 120 file_options[i] &= 1 << 1 ^ 0xFFFFFFFF; 121 } 122 } 123 file_name[i] = fileName[i].getIndex(); 124 } 125 } 126 127 private int[] flatten(final byte[][] bytes) { 128 int total = 0; 129 for (final byte[] element : bytes) { 130 total += element.length; 131 } 132 final int[] band = new int[total]; 133 int index = 0; 134 for (final byte[] element : bytes) { 135 for (final byte element2 : element) { 136 band[index++] = element2 & 0xFF; 137 } 138 } 139 return band; 140 } 141 142 @Override 143 public void pack(final OutputStream out) throws IOException, Pack200Exception { 144 PackingUtils.log("Writing file bands..."); 145 byte[] encodedBand = encodeBandInt("file_name", file_name, Codec.UNSIGNED5); 146 out.write(encodedBand); 147 PackingUtils.log("Wrote " + encodedBand.length + " bytes from file_name[" + file_name.length + "]"); 148 149 encodedBand = encodeFlags("file_size", file_size, Codec.UNSIGNED5, Codec.UNSIGNED5, segmentHeader.have_file_size_hi()); 150 out.write(encodedBand); 151 PackingUtils.log("Wrote " + encodedBand.length + " bytes from file_size[" + file_size.length + "]"); 152 153 if (segmentHeader.have_file_modtime()) { 154 encodedBand = encodeBandInt("file_modtime", file_modtime, Codec.DELTA5); 155 out.write(encodedBand); 156 PackingUtils.log("Wrote " + encodedBand.length + " bytes from file_modtime[" + file_modtime.length + "]"); 157 } 158 if (segmentHeader.have_file_options()) { 159 encodedBand = encodeBandInt("file_options", file_options, Codec.UNSIGNED5); 160 out.write(encodedBand); 161 PackingUtils.log("Wrote " + encodedBand.length + " bytes from file_options[" + file_options.length + "]"); 162 } 163 164 encodedBand = encodeBandInt("file_bits", flatten(file_bits), Codec.BYTE1); 165 out.write(encodedBand); 166 PackingUtils.log("Wrote " + encodedBand.length + " bytes from file_bits[" + file_bits.length + "]"); 167 } 168 169}