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 PackingLogger packingLogger;
045
046    static {
047        packingLogger = new PackingLogger("org.harmony.apache.pack200", null);
048        LogManager.getLogManager().addLogger(packingLogger);
049    }
050
051    private static class PackingLogger extends Logger {
052
053        private boolean verbose = false;
054
055        protected PackingLogger(final String name, final String resourceBundleName) {
056            super(name, resourceBundleName);
057        }
058
059        @Override
060        public void log(final LogRecord logRecord) {
061            if (verbose) {
062                super.log(logRecord);
063            }
064        }
065
066        public void setVerbose(final boolean isVerbose) {
067            verbose = isVerbose;
068        }
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    public static void log(final String message) {
084        packingLogger.log(Level.INFO, message);
085    }
086
087    /**
088     * When effort is 0, the packer copies through the original jar input stream without compression
089     *
090     * @param jarInputStream the jar input stream
091     * @param outputStream the jar output stream
092     * @throws IOException If an I/O error occurs.
093     */
094    public static void copyThroughJar(final JarInputStream jarInputStream, final OutputStream outputStream)
095                        throws IOException {
096                final Manifest manifest = jarInputStream.getManifest();
097                try (final JarOutputStream jarOutputStream = new JarOutputStream(outputStream, manifest)) {
098                        jarOutputStream.setComment("PACK200");
099                        log("Packed " + JarFile.MANIFEST_NAME);
100
101                        final byte[] bytes = new byte[16384];
102                        JarEntry jarEntry;
103                        int bytesRead;
104                        while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
105                                jarOutputStream.putNextEntry(jarEntry);
106                                while ((bytesRead = jarInputStream.read(bytes)) != -1) {
107                                        jarOutputStream.write(bytes, 0, bytesRead);
108                                }
109                                log("Packed " + jarEntry.getName());
110                        }
111                        jarInputStream.close();
112                }
113        }
114
115    /**
116     * When effort is 0, the packer copys through the original jar file without compression
117     *
118     * @param jarFile the input jar file
119     * @param outputStream the jar output stream
120     * @throws IOException If an I/O error occurs.
121     */
122        public static void copyThroughJar(final JarFile jarFile, final OutputStream outputStream) throws IOException {
123                try (final JarOutputStream jarOutputStream = new JarOutputStream(outputStream)) {
124                        jarOutputStream.setComment("PACK200");
125                        final byte[] bytes = new byte[16384];
126                        final Enumeration<JarEntry> entries = jarFile.entries();
127                        while (entries.hasMoreElements()) {
128                                JarEntry jarEntry = entries.nextElement();
129                                jarOutputStream.putNextEntry(jarEntry);
130                                try (InputStream inputStream = jarFile.getInputStream(jarEntry)) {
131                                        int bytesRead;
132                                        while ((bytesRead = inputStream.read(bytes)) != -1) {
133                                                jarOutputStream.write(bytes, 0, bytesRead);
134                                        }
135                                        jarOutputStream.closeEntry();
136                                        log("Packed " + jarEntry.getName());
137                                }
138                        }
139                        jarFile.close();
140                }
141        }
142
143    public static List<PackingFile> getPackingFileListFromJar(final JarInputStream jarInputStream, final boolean keepFileOrder)
144        throws IOException {
145        final List<PackingFile> packingFileList = new ArrayList<>();
146
147        // add manifest file
148        final Manifest manifest = jarInputStream.getManifest();
149        if (manifest != null) {
150            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
151            manifest.write(baos);
152            packingFileList.add(new PackingFile(JarFile.MANIFEST_NAME, baos.toByteArray(), 0));
153        }
154
155        // add rest of entries in the jar
156        JarEntry jarEntry;
157        byte[] bytes;
158        while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
159            bytes = readJarEntry(jarEntry, new BufferedInputStream(jarInputStream));
160            packingFileList.add(new PackingFile(bytes, jarEntry));
161        }
162
163        // check whether it need reorder packing file list
164        if (!keepFileOrder) {
165            reorderPackingFiles(packingFileList);
166        }
167        return packingFileList;
168    }
169
170    public static List<PackingFile> getPackingFileListFromJar(final JarFile jarFile, final boolean keepFileOrder)
171                        throws IOException {
172                final List<PackingFile> packingFileList = new ArrayList<>();
173                final Enumeration<JarEntry> jarEntries = jarFile.entries();
174                while (jarEntries.hasMoreElements()) {
175                        final JarEntry jarEntry = jarEntries.nextElement();
176                        try (InputStream inputStream = jarFile.getInputStream(jarEntry)) {
177                                final byte[] bytes = readJarEntry(jarEntry, new BufferedInputStream(inputStream));
178                                packingFileList.add(new PackingFile(bytes, jarEntry));
179                        }
180                }
181
182                // check whether it need reorder packing file list
183                if (!keepFileOrder) {
184                        reorderPackingFiles(packingFileList);
185                }
186                return packingFileList;
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}