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