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