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.IOException;
020import java.io.OutputStream;
021import java.util.Arrays;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Set;
025import java.util.TimeZone;
026
027import org.apache.commons.compress.harmony.pack200.Archive.PackingFile;
028import org.apache.commons.compress.harmony.pack200.Archive.SegmentUnit;
029import org.apache.commons.compress.utils.ExactMath;
030import org.objectweb.asm.ClassReader;
031
032/**
033 * 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
034 * described in the specification.
035 */
036public class FileBands extends BandSet {
037
038    private final CPUTF8[] fileName;
039    private int[] file_name;
040    private final int[] file_modtime;
041    private final long[] file_size;
042    private final int[] file_options;
043    private final byte[][] file_bits;
044    private final List<PackingFile> fileList;
045    private final PackingOptions options;
046    private final CpBands cpBands;
047
048    public FileBands(final CpBands cpBands, final SegmentHeader segmentHeader, final PackingOptions options, final SegmentUnit segmentUnit, final int effort) {
049        super(effort, segmentHeader);
050        fileList = segmentUnit.getFileList();
051        this.options = options;
052        this.cpBands = cpBands;
053        final int size = fileList.size();
054        fileName = new CPUTF8[size];
055        file_modtime = new int[size];
056        file_size = new long[size];
057        file_options = new int[size];
058        int totalSize = 0;
059        file_bits = new byte[size][];
060        final int archiveModtime = segmentHeader.getArchive_modtime();
061
062        final Set<String> classNames = new HashSet<>();
063        for (final ClassReader reader : segmentUnit.getClassList()) {
064            classNames.add(reader.getClassName());
065        }
066        final CPUTF8 emptyString = cpBands.getCPUtf8("");
067        long modtime;
068        int latestModtime = Integer.MIN_VALUE;
069        final boolean isLatest = !PackingOptions.KEEP.equals(options.getModificationTime());
070        for (int i = 0; i < size; i++) {
071            final PackingFile packingFile = fileList.get(i);
072            final String name = packingFile.getName();
073            if (name.endsWith(".class") && !options.isPassFile(name)) {
074                file_options[i] |= 1 << 1;
075                if (classNames.contains(name.substring(0, name.length() - 6))) {
076                    fileName[i] = emptyString;
077                } else {
078                    fileName[i] = cpBands.getCPUtf8(name);
079                }
080            } else {
081                fileName[i] = cpBands.getCPUtf8(name);
082            }
083            // set deflate_hint for file element
084            if (options.isKeepDeflateHint() && packingFile.isDefalteHint()) {
085                file_options[i] |= 0x1;
086            }
087            final byte[] bytes = packingFile.getContents();
088            file_size[i] = bytes.length;
089            totalSize = ExactMath.add(totalSize, file_size[i]);
090
091            // update modification time
092            modtime = (packingFile.getModtime() + TimeZone.getDefault().getRawOffset()) / 1000L;
093            file_modtime[i] = (int) (modtime - archiveModtime);
094            if (isLatest && latestModtime < file_modtime[i]) {
095                latestModtime = file_modtime[i];
096            }
097
098            file_bits[i] = packingFile.getContents();
099        }
100
101        if (isLatest) {
102            Arrays.fill(file_modtime, latestModtime);
103        }
104    }
105
106    /**
107     * 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
108     * while classes were being read.
109     */
110    public void finaliseBands() {
111        file_name = new int[fileName.length];
112        for (int i = 0; i < file_name.length; i++) {
113            if (fileName[i].equals(cpBands.getCPUtf8(""))) {
114                final PackingFile packingFile = fileList.get(i);
115                final String name = packingFile.getName();
116                if (options.isPassFile(name)) {
117                    fileName[i] = cpBands.getCPUtf8(name);
118                    file_options[i] &= 1 << 1 ^ 0xFFFFFFFF;
119                }
120            }
121            file_name[i] = fileName[i].getIndex();
122        }
123    }
124
125    private int[] flatten(final byte[][] bytes) {
126        int total = 0;
127        for (final byte[] element : bytes) {
128            total += element.length;
129        }
130        final int[] band = new int[total];
131        int index = 0;
132        for (final byte[] element : bytes) {
133            for (final byte element2 : element) {
134                band[index++] = element2 & 0xFF;
135            }
136        }
137        return band;
138    }
139
140    @Override
141    public void pack(final OutputStream out) throws IOException, Pack200Exception {
142        PackingUtils.log("Writing file bands...");
143        byte[] encodedBand = encodeBandInt("file_name", file_name, Codec.UNSIGNED5);
144        out.write(encodedBand);
145        PackingUtils.log("Wrote " + encodedBand.length + " bytes from file_name[" + file_name.length + "]");
146
147        encodedBand = encodeFlags("file_size", file_size, Codec.UNSIGNED5, Codec.UNSIGNED5, segmentHeader.have_file_size_hi());
148        out.write(encodedBand);
149        PackingUtils.log("Wrote " + encodedBand.length + " bytes from file_size[" + file_size.length + "]");
150
151        if (segmentHeader.have_file_modtime()) {
152            encodedBand = encodeBandInt("file_modtime", file_modtime, Codec.DELTA5);
153            out.write(encodedBand);
154            PackingUtils.log("Wrote " + encodedBand.length + " bytes from file_modtime[" + file_modtime.length + "]");
155        }
156        if (segmentHeader.have_file_options()) {
157            encodedBand = encodeBandInt("file_options", file_options, Codec.UNSIGNED5);
158            out.write(encodedBand);
159            PackingUtils.log("Wrote " + encodedBand.length + " bytes from file_options[" + file_options.length + "]");
160        }
161
162        encodedBand = encodeBandInt("file_bits", flatten(file_bits), Codec.BYTE1);
163        out.write(encodedBand);
164        PackingUtils.log("Wrote " + encodedBand.length + " bytes from file_bits[" + file_bits.length + "]");
165    }
166
167}