001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.commons.compress.harmony.pack200; 020 021import java.io.BufferedInputStream; 022import java.io.ByteArrayOutputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026import java.util.ArrayList; 027import java.util.Enumeration; 028import java.util.Iterator; 029import java.util.List; 030import java.util.jar.JarEntry; 031import java.util.jar.JarFile; 032import java.util.jar.JarInputStream; 033import java.util.jar.JarOutputStream; 034import java.util.jar.Manifest; 035import java.util.logging.FileHandler; 036import java.util.logging.Level; 037import java.util.logging.LogManager; 038import java.util.logging.LogRecord; 039import java.util.logging.Logger; 040import java.util.logging.SimpleFormatter; 041 042import org.apache.commons.compress.harmony.pack200.Archive.PackingFile; 043 044public class PackingUtils { 045 046 private static final class PackingLogger extends Logger { 047 048 private boolean verbose; 049 050 protected PackingLogger(final String name, final String resourceBundleName) { 051 super(name, resourceBundleName); 052 } 053 054 @Override 055 public void log(final LogRecord logRecord) { 056 if (verbose) { 057 super.log(logRecord); 058 } 059 } 060 061 private void setVerbose(final boolean isVerbose) { 062 verbose = isVerbose; 063 } 064 } 065 066 private static PackingLogger packingLogger; 067 private static FileHandler fileHandler; 068 069 static { 070 packingLogger = new PackingLogger("org.harmony.apache.pack200", null); 071 LogManager.getLogManager().addLogger(packingLogger); 072 } 073 074 public static void config(final PackingOptions options) throws IOException { 075 final String logFileName = options != null ? options.getLogFile() : null; 076 if (fileHandler != null) { 077 fileHandler.close(); 078 } 079 if (logFileName != null) { 080 fileHandler = new FileHandler(logFileName, false); 081 fileHandler.setFormatter(new SimpleFormatter()); 082 packingLogger.addHandler(fileHandler); 083 packingLogger.setUseParentHandlers(false); 084 } 085 if (options != null) { 086 packingLogger.setVerbose(options.isVerbose()); 087 } 088 } 089 090 /** 091 * When effort is 0, the packer copys through the original jar file without compression 092 * 093 * @param jarFile the input jar file 094 * @param outputStream the jar output stream 095 * @throws IOException If an I/O error occurs. 096 */ 097 public static void copyThroughJar(final JarFile jarFile, final OutputStream outputStream) throws IOException { 098 try (JarOutputStream jarOutputStream = new JarOutputStream(outputStream)) { 099 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}