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