001: /*
002: * ProGuard -- shrinking, optimization, obfuscation, and preverification
003: * of Java bytecode.
004: *
005: * Copyright (c) 2002-2007 Eric Lafortune (eric@graphics.cornell.edu)
006: *
007: * This program is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU General Public License as published by the Free
009: * Software Foundation; either version 2 of the License, or (at your option)
010: * any later version.
011: *
012: * This program is distributed in the hope that it will be useful, but WITHOUT
013: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
015: * more details.
016: *
017: * You should have received a copy of the GNU General Public License along
018: * with this program; if not, write to the Free Software Foundation, Inc.,
019: * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: */
021: package proguard.io;
022:
023: import java.io.*;
024: import java.util.*;
025: import java.util.jar.*;
026: import java.util.zip.*;
027:
028: /**
029: * This DataEntryWriter sends data entries to a given jar/zip file.
030: * The manifest and comment properties can optionally be set.
031: *
032: * @author Eric Lafortune
033: */
034: public class JarWriter implements DataEntryWriter, Finisher {
035: private final DataEntryWriter dataEntryWriter;
036: private final Manifest manifest;
037: private final String comment;
038:
039: private OutputStream currentParentOutputStream;
040: private ZipOutputStream currentJarOutputStream;
041: private Finisher currentFinisher;
042: private DataEntry currentDataEntry;
043:
044: // The names of the jar entries that are already in the jar.
045: private final Set jarEntryNames = new HashSet();
046:
047: /**
048: * Creates a new JarWriter without manifest or comment.
049: */
050: public JarWriter(DataEntryWriter dataEntryWriter) {
051: this (dataEntryWriter, null, null);
052: }
053:
054: /**
055: * Creates a new JarWriter.
056: */
057: public JarWriter(DataEntryWriter dataEntryWriter,
058: Manifest manifest, String comment) {
059: this .dataEntryWriter = dataEntryWriter;
060: this .manifest = manifest;
061: this .comment = comment;
062: }
063:
064: // Implementations for DataEntryWriter.
065:
066: public OutputStream getOutputStream(DataEntry dataEntry)
067: throws IOException {
068: return getOutputStream(dataEntry, null);
069: }
070:
071: public OutputStream getOutputStream(DataEntry dataEntry,
072: Finisher finisher) throws IOException {
073: // Get the parent stream, new or exisiting.
074: // This may finish our own jar output stream.
075: OutputStream parentOutputStream = dataEntryWriter
076: .getOutputStream(dataEntry.getParent(), this );
077:
078: // Did we get a stream?
079: if (parentOutputStream == null) {
080: return null;
081: }
082:
083: // Do we need a new stream?
084: if (currentParentOutputStream == null) {
085: currentParentOutputStream = parentOutputStream;
086:
087: // Create a new jar stream, with a manifest, if set.
088: currentJarOutputStream = manifest != null ? new JarOutputStream(
089: parentOutputStream, manifest)
090: : new ZipOutputStream(parentOutputStream);
091:
092: // Add a comment, if set.
093: if (comment != null) {
094: currentJarOutputStream.setComment(comment);
095: }
096: }
097:
098: // Do we need a new entry?
099: if (!dataEntry.equals(currentDataEntry)) {
100: // Close the previous ZIP entry, if any.
101: closeEntry();
102:
103: // Get the entry name.
104: String name = dataEntry.getName();
105:
106: // We have to check if the name is already used, because ZipOutputStream
107: // doesn't handle this case properly (it throws an exception which can
108: // be caught, but the ZipDataEntry is remembered anyway).
109: if (!jarEntryNames.add(name)) {
110: throw new IOException("Duplicate zip entry ["
111: + dataEntry + "]");
112: }
113:
114: // Create a new entry.
115: currentJarOutputStream.putNextEntry(new ZipEntry(name));
116:
117: currentFinisher = finisher;
118: currentDataEntry = dataEntry;
119: }
120:
121: return currentJarOutputStream;
122: }
123:
124: public void finish() throws IOException {
125: // Finish the entire ZIP stream, if any.
126: if (currentJarOutputStream != null) {
127: // Close the previous ZIP entry, if any.
128: closeEntry();
129:
130: // Finish the entire ZIP stream.
131: currentJarOutputStream.finish();
132: currentJarOutputStream = null;
133: currentParentOutputStream = null;
134: jarEntryNames.clear();
135: }
136: }
137:
138: public void close() throws IOException {
139: // Close the parent stream.
140: dataEntryWriter.close();
141: }
142:
143: // Small utility methods.
144:
145: /**
146: * Closes the previous ZIP entry, if any.
147: */
148: private void closeEntry() throws IOException {
149: if (currentDataEntry != null) {
150: // Let any finisher finish up first.
151: if (currentFinisher != null) {
152: currentFinisher.finish();
153: currentFinisher = null;
154: }
155:
156: currentJarOutputStream.closeEntry();
157: currentDataEntry = null;
158: }
159: }
160: }
|