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}