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}