/* * ProGuard -- shrinking, optimization, obfuscation, and preverification * of Java bytecode. * * Copyright (c) 2002-2013 Eric Lafortune (eric@graphics.cornell.edu) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package proguard.io; import proguard.classfile.ClassConstants; import java.io.*; import java.util.*; import java.util.jar.*; import java.util.zip.*; /** * This DataEntryWriter sends data entries to a given jar/zip file. * The manifest and comment properties can optionally be set. * * @author Eric Lafortune */ public class JarWriter implements DataEntryWriter, Finisher { private final DataEntryWriter dataEntryWriter; private final Manifest manifest; private final String comment; private OutputStream currentParentOutputStream; private ZipOutputStream currentJarOutputStream; private Finisher currentFinisher; private DataEntry currentDataEntry; // The names of the jar entries that are already in the jar. private final Set jarEntryNames = new HashSet(); /** * Creates a new JarWriter without manifest or comment. */ public JarWriter(DataEntryWriter dataEntryWriter) { this(dataEntryWriter, null, null); } /** * Creates a new JarWriter. */ public JarWriter(DataEntryWriter dataEntryWriter, Manifest manifest, String comment) { this.dataEntryWriter = dataEntryWriter; this.manifest = manifest; this.comment = comment; } // Implementations for DataEntryWriter. public boolean createDirectory(DataEntry dataEntry) throws IOException { //Make sure we can start with a new entry. if (!prepareEntry(dataEntry)) { return false; } // Close the previous ZIP entry, if any. closeEntry(); // Get the directory entry name. String name = dataEntry.getName() + ClassConstants.INTERNAL_PACKAGE_SEPARATOR; // We have to check if the name is already used, because // ZipOutputStream doesn't handle this case properly (it throws // an exception which can be caught, but the ZipDataEntry is // remembered anyway). if (jarEntryNames.add(name)) { // Create a new directory entry. currentJarOutputStream.putNextEntry(new ZipEntry(name)); currentJarOutputStream.closeEntry(); } // Clear the finisher. currentFinisher = null; currentDataEntry = null; return true; } public OutputStream getOutputStream(DataEntry dataEntry) throws IOException { return getOutputStream(dataEntry, null); } public OutputStream getOutputStream(DataEntry dataEntry, Finisher finisher) throws IOException { //Make sure we can start with a new entry. if (!prepareEntry(dataEntry)) { return null; } // Do we need a new entry? if (!dataEntry.equals(currentDataEntry)) { // Close the previous ZIP entry, if any. closeEntry(); // Get the entry name. String name = dataEntry.getName(); // We have to check if the name is already used, because // ZipOutputStream doesn't handle this case properly (it throws // an exception which can be caught, but the ZipDataEntry is // remembered anyway). if (!jarEntryNames.add(name)) { throw new IOException("Duplicate zip entry ["+dataEntry+"]"); } // Create a new entry. currentJarOutputStream.putNextEntry(new ZipEntry(name)); // Set up the finisher for the entry. currentFinisher = finisher; currentDataEntry = dataEntry; } return currentJarOutputStream; } public void finish() throws IOException { // Finish the entire ZIP stream, if any. if (currentJarOutputStream != null) { // Close the previous ZIP entry, if any. closeEntry(); // Finish the entire ZIP stream. currentJarOutputStream.finish(); currentJarOutputStream = null; currentParentOutputStream = null; jarEntryNames.clear(); } } public void close() throws IOException { // Close the parent stream. dataEntryWriter.close(); } // Small utility methods. /** * Makes sure the current output stream is set up for the given entry. */ private boolean prepareEntry(DataEntry dataEntry) throws IOException { // Get the parent stream, new or exisiting. // This may finish our own jar output stream. OutputStream parentOutputStream = dataEntryWriter.getOutputStream(dataEntry.getParent(), this); // Did we get a stream? if (parentOutputStream == null) { return false; } // Do we need a new stream? if (currentParentOutputStream == null) { currentParentOutputStream = parentOutputStream; // Create a new jar stream, with a manifest, if set. currentJarOutputStream = manifest != null ? new JarOutputStream(parentOutputStream, manifest) : new ZipOutputStream(parentOutputStream); // Add a comment, if set. if (comment != null) { currentJarOutputStream.setComment(comment); } } return true; } /** * Closes the previous ZIP entry, if any. */ private void closeEntry() throws IOException { if (currentDataEntry != null) { // Let any finisher finish up first. if (currentFinisher != null) { currentFinisher.finish(); currentFinisher = null; } currentJarOutputStream.closeEntry(); currentDataEntry = null; } } }