1/* 2 * ProGuard -- shrinking, optimization, obfuscation, and preverification 3 * of Java bytecode. 4 * 5 * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu) 6 * 7 * This program is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License as published by the Free 9 * Software Foundation; either version 2 of the License, or (at your option) 10 * any later version. 11 * 12 * This program is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 * more details. 16 * 17 * You should have received a copy of the GNU General Public License along 18 * with this program; if not, write to the Free Software Foundation, Inc., 19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 */ 21package proguard.io; 22 23import proguard.classfile.ClassConstants; 24 25import java.io.*; 26import java.util.*; 27import java.util.jar.*; 28import java.util.zip.*; 29 30 31/** 32 * This DataEntryWriter sends data entries to a given jar/zip file. 33 * The manifest and comment properties can optionally be set. 34 * 35 * @author Eric Lafortune 36 */ 37public class JarWriter implements DataEntryWriter, Finisher 38{ 39 private final DataEntryWriter dataEntryWriter; 40 private final Manifest manifest; 41 private final String comment; 42 43 private OutputStream currentParentOutputStream; 44 private ZipOutputStream currentJarOutputStream; 45 private Finisher currentFinisher; 46 private DataEntry currentDataEntry; 47 48 // The names of the jar entries that are already in the jar. 49 private final Set jarEntryNames = new HashSet(); 50 51 52 /** 53 * Creates a new JarWriter without manifest or comment. 54 */ 55 public JarWriter(DataEntryWriter dataEntryWriter) 56 { 57 this(dataEntryWriter, null, null); 58 } 59 60 61 /** 62 * Creates a new JarWriter. 63 */ 64 public JarWriter(DataEntryWriter dataEntryWriter, 65 Manifest manifest, 66 String comment) 67 { 68 this.dataEntryWriter = dataEntryWriter; 69 this.manifest = manifest; 70 this.comment = comment; 71 } 72 73 74 // Implementations for DataEntryWriter. 75 76 public boolean createDirectory(DataEntry dataEntry) throws IOException 77 { 78 //Make sure we can start with a new entry. 79 if (!prepareEntry(dataEntry)) 80 { 81 return false; 82 } 83 84 // Close the previous ZIP entry, if any. 85 closeEntry(); 86 87 // Get the directory entry name. 88 String name = dataEntry.getName() + ClassConstants.INTERNAL_PACKAGE_SEPARATOR; 89 90 // We have to check if the name is already used, because 91 // ZipOutputStream doesn't handle this case properly (it throws 92 // an exception which can be caught, but the ZipDataEntry is 93 // remembered anyway). 94 if (jarEntryNames.add(name)) 95 { 96 // Create a new directory entry. 97 currentJarOutputStream.putNextEntry(new ZipEntry(name)); 98 currentJarOutputStream.closeEntry(); 99 } 100 101 // Clear the finisher. 102 currentFinisher = null; 103 currentDataEntry = null; 104 105 return true; 106 } 107 108 109 public OutputStream getOutputStream(DataEntry dataEntry) throws IOException 110 { 111 return getOutputStream(dataEntry, null); 112 } 113 114 115 public OutputStream getOutputStream(DataEntry dataEntry, 116 Finisher finisher) throws IOException 117 { 118 //Make sure we can start with a new entry. 119 if (!prepareEntry(dataEntry)) 120 { 121 return null; 122 } 123 124 // Do we need a new entry? 125 if (!dataEntry.equals(currentDataEntry)) 126 { 127 // Close the previous ZIP entry, if any. 128 closeEntry(); 129 130 // Get the entry name. 131 String name = dataEntry.getName(); 132 133 // We have to check if the name is already used, because 134 // ZipOutputStream doesn't handle this case properly (it throws 135 // an exception which can be caught, but the ZipDataEntry is 136 // remembered anyway). 137 if (!jarEntryNames.add(name)) 138 { 139 throw new IOException("Duplicate zip entry ["+dataEntry+"]"); 140 } 141 142 // Create a new entry. 143 currentJarOutputStream.putNextEntry(new ZipEntry(name)); 144 145 // Set up the finisher for the entry. 146 currentFinisher = finisher; 147 currentDataEntry = dataEntry; 148 } 149 150 return currentJarOutputStream; 151 } 152 153 154 public void finish() throws IOException 155 { 156 // Finish the entire ZIP stream, if any. 157 if (currentJarOutputStream != null) 158 { 159 // Close the previous ZIP entry, if any. 160 closeEntry(); 161 162 // Finish the entire ZIP stream. 163 currentJarOutputStream.finish(); 164 currentJarOutputStream = null; 165 currentParentOutputStream = null; 166 jarEntryNames.clear(); 167 } 168 } 169 170 171 public void close() throws IOException 172 { 173 // Close the parent stream. 174 dataEntryWriter.close(); 175 } 176 177 178 // Small utility methods. 179 180 /** 181 * Makes sure the current output stream is set up for the given entry. 182 */ 183 private boolean prepareEntry(DataEntry dataEntry) throws IOException 184 { 185 // Get the parent stream, new or exisiting. 186 // This may finish our own jar output stream. 187 OutputStream parentOutputStream = 188 dataEntryWriter.getOutputStream(dataEntry.getParent(), this); 189 190 // Did we get a stream? 191 if (parentOutputStream == null) 192 { 193 return false; 194 } 195 196 // Do we need a new stream? 197 if (currentParentOutputStream == null) 198 { 199 currentParentOutputStream = parentOutputStream; 200 201 // Create a new jar stream, with a manifest, if set. 202 currentJarOutputStream = manifest != null ? 203 new JarOutputStream(parentOutputStream, manifest) : 204 new ZipOutputStream(parentOutputStream); 205 206 // Add a comment, if set. 207 if (comment != null) 208 { 209 currentJarOutputStream.setComment(comment); 210 } 211 } 212 213 return true; 214 } 215 216 217 /** 218 * Closes the previous ZIP entry, if any. 219 */ 220 private void closeEntry() throws IOException 221 { 222 if (currentDataEntry != null) 223 { 224 // Let any finisher finish up first. 225 if (currentFinisher != null) 226 { 227 currentFinisher.finish(); 228 currentFinisher = null; 229 } 230 231 currentJarOutputStream.closeEntry(); 232 currentDataEntry = null; 233 } 234 } 235} 236