View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.commons.compress.harmony.pack200;
18  
19  import java.io.BufferedInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.util.ArrayList;
25  import java.util.Enumeration;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.jar.JarEntry;
29  import java.util.jar.JarFile;
30  import java.util.jar.JarInputStream;
31  import java.util.jar.JarOutputStream;
32  import java.util.jar.Manifest;
33  import java.util.logging.FileHandler;
34  import java.util.logging.Level;
35  import java.util.logging.LogManager;
36  import java.util.logging.LogRecord;
37  import java.util.logging.Logger;
38  import java.util.logging.SimpleFormatter;
39  
40  import org.apache.commons.compress.harmony.pack200.Archive.PackingFile;
41  
42  public class PackingUtils {
43  
44      private static final class PackingLogger extends Logger {
45  
46          private boolean verbose;
47  
48          protected PackingLogger(final String name, final String resourceBundleName) {
49              super(name, resourceBundleName);
50          }
51  
52          @Override
53          public void log(final LogRecord logRecord) {
54              if (verbose) {
55                  super.log(logRecord);
56              }
57          }
58  
59          private void setVerbose(final boolean isVerbose) {
60              verbose = isVerbose;
61          }
62      }
63  
64      private static PackingLogger packingLogger;
65      private static FileHandler fileHandler;
66  
67      static {
68          packingLogger = new PackingLogger("org.harmony.apache.pack200", null);
69          LogManager.getLogManager().addLogger(packingLogger);
70      }
71  
72      public static void config(final PackingOptions options) throws IOException {
73          final String logFileName = options != null ? options.getLogFile() : null;
74          if (fileHandler != null) {
75              fileHandler.close();
76          }
77          if (logFileName != null) {
78              fileHandler = new FileHandler(logFileName, false);
79              fileHandler.setFormatter(new SimpleFormatter());
80              packingLogger.addHandler(fileHandler);
81              packingLogger.setUseParentHandlers(false);
82          }
83          if (options != null) {
84              packingLogger.setVerbose(options.isVerbose());
85          }
86      }
87  
88      /**
89       * When effort is 0, the packer copys through the original jar file without compression
90       *
91       * @param jarFile      the input jar file
92       * @param outputStream the jar output stream
93       * @throws IOException If an I/O error occurs.
94       */
95      public static void copyThroughJar(final JarFile jarFile, final OutputStream outputStream) throws IOException {
96          try (JarOutputStream jarOutputStream = new JarOutputStream(outputStream)) {
97              jarOutputStream.setComment("PACK200");
98              final byte[] bytes = new byte[16384];
99              final Enumeration<JarEntry> entries = jarFile.entries();
100             while (entries.hasMoreElements()) {
101                 final JarEntry jarEntry = entries.nextElement();
102                 jarOutputStream.putNextEntry(jarEntry);
103                 try (InputStream inputStream = jarFile.getInputStream(jarEntry)) {
104                     int bytesRead;
105                     while ((bytesRead = inputStream.read(bytes)) != -1) {
106                         jarOutputStream.write(bytes, 0, bytesRead);
107                     }
108                     jarOutputStream.closeEntry();
109                     log("Packed " + jarEntry.getName());
110                 }
111             }
112             jarFile.close();
113         }
114     }
115 
116     /**
117      * When effort is 0, the packer copies through the original jar input stream without compression
118      *
119      * @param jarInputStream the jar input stream
120      * @param outputStream   the jar output stream
121      * @throws IOException If an I/O error occurs.
122      */
123     public static void copyThroughJar(final JarInputStream jarInputStream, final OutputStream outputStream) throws IOException {
124         final Manifest manifest = jarInputStream.getManifest();
125         try (JarOutputStream jarOutputStream = new JarOutputStream(outputStream, manifest)) {
126             jarOutputStream.setComment("PACK200");
127             log("Packed " + JarFile.MANIFEST_NAME);
128 
129             final byte[] bytes = new byte[16384];
130             JarEntry jarEntry;
131             int bytesRead;
132             while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
133                 jarOutputStream.putNextEntry(jarEntry);
134                 while ((bytesRead = jarInputStream.read(bytes)) != -1) {
135                     jarOutputStream.write(bytes, 0, bytesRead);
136                 }
137                 log("Packed " + jarEntry.getName());
138             }
139             jarInputStream.close();
140         }
141     }
142 
143     public static List<PackingFile> getPackingFileListFromJar(final JarFile jarFile, final boolean keepFileOrder) throws IOException {
144         final List<PackingFile> packingFileList = new ArrayList<>();
145         final Enumeration<JarEntry> jarEntries = jarFile.entries();
146         while (jarEntries.hasMoreElements()) {
147             final JarEntry jarEntry = jarEntries.nextElement();
148             try (InputStream inputStream = jarFile.getInputStream(jarEntry)) {
149                 final byte[] bytes = readJarEntry(jarEntry, new BufferedInputStream(inputStream));
150                 packingFileList.add(new PackingFile(bytes, jarEntry));
151             }
152         }
153 
154         // check whether it need reorder packing file list
155         if (!keepFileOrder) {
156             reorderPackingFiles(packingFileList);
157         }
158         return packingFileList;
159     }
160 
161     public static List<PackingFile> getPackingFileListFromJar(final JarInputStream jarInputStream, final boolean keepFileOrder) throws IOException {
162         final List<PackingFile> packingFileList = new ArrayList<>();
163 
164         // add manifest file
165         final Manifest manifest = jarInputStream.getManifest();
166         if (manifest != null) {
167             final ByteArrayOutputStream baos = new ByteArrayOutputStream();
168             manifest.write(baos);
169             packingFileList.add(new PackingFile(JarFile.MANIFEST_NAME, baos.toByteArray(), 0));
170         }
171 
172         // add rest of entries in the jar
173         JarEntry jarEntry;
174         byte[] bytes;
175         while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
176             bytes = readJarEntry(jarEntry, new BufferedInputStream(jarInputStream));
177             packingFileList.add(new PackingFile(bytes, jarEntry));
178         }
179 
180         // check whether it need reorder packing file list
181         if (!keepFileOrder) {
182             reorderPackingFiles(packingFileList);
183         }
184         return packingFileList;
185     }
186 
187     public static void log(final String message) {
188         packingLogger.log(Level.INFO, message);
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 IllegalArgumentException("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 IllegalArgumentException("Error reading from stream");
203         }
204         return bytes;
205     }
206 
207     private static void reorderPackingFiles(final List<PackingFile> packingFileList) {
208         final Iterator<PackingFile> iterator = packingFileList.iterator();
209         while (iterator.hasNext()) {
210             final PackingFile packingFile = iterator.next();
211             if (packingFile.isDirectory()) {
212                 // remove directory entries
213                 iterator.remove();
214             }
215         }
216 
217         // Sort files by name, "META-INF/MANIFEST.MF" should be put in the 1st
218         // position
219         packingFileList.sort((arg0, arg1) -> {
220             final String fileName0 = arg0.getName();
221             final String fileName1 = arg1.getName();
222             if (fileName0.equals(fileName1)) {
223                 return 0;
224             }
225             if (JarFile.MANIFEST_NAME.equals(fileName0)) {
226                 return -1;
227             }
228             if (JarFile.MANIFEST_NAME.equals(fileName1)) {
229                 return 1;
230             }
231             return fileName0.compareTo(fileName1);
232         });
233     }
234 
235 }