    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.BufferedInputStream;
    022import java.io.ByteArrayOutputStream;
    023import java.io.IOException;
    024import java.io.InputStream;
    025import java.io.OutputStream;
    026import java.util.ArrayList;
    027import java.util.Enumeration;
    028import java.util.Iterator;
    029import java.util.List;
    030import java.util.jar.JarEntry;
    031import java.util.jar.JarFile;
    032import java.util.jar.JarInputStream;
    033import java.util.jar.JarOutputStream;
    034import java.util.jar.Manifest;
    035import java.util.logging.FileHandler;
    036import java.util.logging.Level;
    037import java.util.logging.LogManager;
    038import java.util.logging.LogRecord;
    039import java.util.logging.Logger;
    040import java.util.logging.SimpleFormatter;
    041
    042import org.apache.commons.compress.harmony.pack200.Archive.PackingFile;
    043
    044public class PackingUtils {
    045
    046    private static final class PackingLogger extends Logger {
    047
    048        private boolean verbose;
    049
    050        protected PackingLogger(final String name, final String resourceBundleName) {
    051            super(name, resourceBundleName);
    052        }
    053
    054        @Override
    055        public void log(final LogRecord logRecord) {
    056            if (verbose) {
    057                super.log(logRecord);
    058            }
    059        }
    060
    061        private void setVerbose(final boolean isVerbose) {
    062            verbose = isVerbose;
    063        }
    064    }
    065
    066    private static PackingLogger packingLogger;
    067    private static FileHandler fileHandler;
    068
    069    static {
    070        packingLogger = new PackingLogger("org.harmony.apache.pack200", null);
    071        LogManager.getLogManager().addLogger(packingLogger);
    072    }
    073
    074    public static void config(final PackingOptions options) throws IOException {
    075        final String logFileName = options != null ? options.getLogFile() : null;
    076        if (fileHandler != null) {
    077            fileHandler.close();
    078        }
    079        if (logFileName != null) {
    080            fileHandler = new FileHandler(logFileName, false);
    081            fileHandler.setFormatter(new SimpleFormatter());
    082            packingLogger.addHandler(fileHandler);
    083            packingLogger.setUseParentHandlers(false);
    084        }
    085        if (options != null) {
    086            packingLogger.setVerbose(options.isVerbose());
    087        }
    088    }
    089
    090    /**
    091     * When effort is 0, the packer copys through the original jar file without compression
    092     *
    093     * @param jarFile      the input jar file
    094     * @param outputStream the jar output stream
    095     * @throws IOException If an I/O error occurs.
    096     */
    097    public static void copyThroughJar(final JarFile jarFile, final OutputStream outputStream) throws IOException {
    098        try (JarOutputStream jarOutputStream = new JarOutputStream(outputStream)) {
    099            jarOutputStream.setComment("PACK200");
    100            final byte[] bytes = new byte[16384];
    101            final Enumeration<JarEntry> entries = jarFile.entries();
    102            while (entries.hasMoreElements()) {
    103                final JarEntry jarEntry = entries.nextElement();
    104                jarOutputStream.putNextEntry(jarEntry);
    105                try (InputStream inputStream = jarFile.getInputStream(jarEntry)) {
    106                    int bytesRead;
    107                    while ((bytesRead = inputStream.read(bytes)) != -1) {
    108                        jarOutputStream.write(bytes, 0, bytesRead);
    109                    }
    110                    jarOutputStream.closeEntry();
    111                    log("Packed " + jarEntry.getName());
    112                }
    113            }
    114            jarFile.close();
    115        }
    116    }
    117
    118    /**
    119     * When effort is 0, the packer copies through the original jar input stream without compression
    120     *
    121     * @param jarInputStream the jar input stream
    122     * @param outputStream   the jar output stream
    123     * @throws IOException If an I/O error occurs.
    124     */
    125    public static void copyThroughJar(final JarInputStream jarInputStream, final OutputStream outputStream) throws IOException {
    126        final Manifest manifest = jarInputStream.getManifest();
    127        try (JarOutputStream jarOutputStream = new JarOutputStream(outputStream, manifest)) {
    128            jarOutputStream.setComment("PACK200");
    129            log("Packed " + JarFile.MANIFEST_NAME);
    130
    131            final byte[] bytes = new byte[16384];
    132            JarEntry jarEntry;
    133            int bytesRead;
    134            while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
    135                jarOutputStream.putNextEntry(jarEntry);
    136                while ((bytesRead = jarInputStream.read(bytes)) != -1) {
    137                    jarOutputStream.write(bytes, 0, bytesRead);
    138                }
    139                log("Packed " + jarEntry.getName());
    140            }
    141            jarInputStream.close();
    142        }
    143    }
    144
    145    public static List<PackingFile> getPackingFileListFromJar(final JarFile jarFile, final boolean keepFileOrder) throws IOException {
    146        final List<PackingFile> packingFileList = new ArrayList<>();
    147        final Enumeration<JarEntry> jarEntries = jarFile.entries();
    148        while (jarEntries.hasMoreElements()) {
    149            final JarEntry jarEntry = jarEntries.nextElement();
    150            try (InputStream inputStream = jarFile.getInputStream(jarEntry)) {
    151                final byte[] bytes = readJarEntry(jarEntry, new BufferedInputStream(inputStream));
    152                packingFileList.add(new PackingFile(bytes, jarEntry));
    153            }
    154        }
    155
    156        // check whether it need reorder packing file list
    157        if (!keepFileOrder) {
    158            reorderPackingFiles(packingFileList);
    159        }
    160        return packingFileList;
    161    }
    162
    163    public static List<PackingFile> getPackingFileListFromJar(final JarInputStream jarInputStream, final boolean keepFileOrder) throws IOException {
    164        final List<PackingFile> packingFileList = new ArrayList<>();
    165
    166        // add manifest file
    167        final Manifest manifest = jarInputStream.getManifest();
    168        if (manifest != null) {
    169            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
    170            manifest.write(baos);
    171            packingFileList.add(new PackingFile(JarFile.MANIFEST_NAME, baos.toByteArray(), 0));
    172        }
    173
    174        // add rest of entries in the jar
    175        JarEntry jarEntry;
    176        byte[] bytes;
    177        while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
    178            bytes = readJarEntry(jarEntry, new BufferedInputStream(jarInputStream));
    179            packingFileList.add(new PackingFile(bytes, jarEntry));
    180        }
    181
    182        // check whether it need reorder packing file list
    183        if (!keepFileOrder) {
    184            reorderPackingFiles(packingFileList);
    185        }
    186        return packingFileList;
    187    }
    188
    189    public static void log(final String message) {
    190        packingLogger.log(Level.INFO, message);
    191    }
    192
    193    private static byte[] readJarEntry(final JarEntry jarEntry, final InputStream inputStream) throws IOException {
    194        long size = jarEntry.getSize();
    195        if (size > Integer.MAX_VALUE) {
    196            // TODO: Should probably allow this
    197            throw new IllegalArgumentException("Large Class!");
    198        }
    199        if (size < 0) {
    200            size = 0;
    201        }
    202        final byte[] bytes = new byte[(int) size];
    203        if (inputStream.read(bytes) != size) {
    204            throw new IllegalArgumentException("Error reading from stream");
    205        }
    206        return bytes;
    207    }
    208
    209    private static void reorderPackingFiles(final List<PackingFile> packingFileList) {
    210        final Iterator<PackingFile> iterator = packingFileList.iterator();
    211        while (iterator.hasNext()) {
    212            final PackingFile packingFile = iterator.next();
    213            if (packingFile.isDirectory()) {
    214                // remove directory entries
    215                iterator.remove();
    216            }
    217        }
    218
    219        // Sort files by name, "META-INF/MANIFEST.MF" should be put in the 1st
    220        // position
    221        packingFileList.sort((arg0, arg1) -> {
    222            final String fileName0 = arg0.getName();
    223            final String fileName1 = arg1.getName();
    224            if (fileName0.equals(fileName1)) {
    225                return 0;
    226            }
    227            if (JarFile.MANIFEST_NAME.equals(fileName0)) {
    228                return -1;
    229            }
    230            if (JarFile.MANIFEST_NAME.equals(fileName1)) {
    231                return 1;
    232            }
    233            return fileName0.compareTo(fileName1);
    234        });
    235    }
    236
    237}