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}