blob: 5437f28a874766172742f7a16dc497a9bff26dc1 [file] [log] [blame]
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2014 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.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 existing.
// 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;
}
}
}