001: /* Copyright (C) 2004 - 2007 db4objects Inc. http://www.db4o.com
002:
003: This file is part of the db4o open source object database.
004:
005: db4o is free software; you can redistribute it and/or modify it under
006: the terms of version 2 of the GNU General Public License as published
007: by the Free Software Foundation and as clarified by db4objects' GPL
008: interpretation policy, available at
009: http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
010: Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
011: Suite 350, San Mateo, CA 94403, USA.
012:
013: db4o is distributed in the hope that it will be useful, but WITHOUT ANY
014: WARRANTY; without even the implied warranty of MERCHANTABILITY or
015: FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
016: for more details.
017:
018: You should have received a copy of the GNU General Public License along
019: with this program; if not, write to the Free Software Foundation, Inc.,
020: 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
021: package EDU.purdue.cs.bloat.file;
022:
023: import java.io.*;
024: import java.util.*;
025: import java.util.jar.*;
026: import java.util.zip.*;
027:
028: import EDU.purdue.cs.bloat.reflect.*;
029:
030: /**
031: * Does a lot of the same stuff as <tt>ClassFileLoader</tt>, but classes are
032: * committed to a JAR file instead of regular files.
033: */
034: public class JarFileCommitter extends ClassFileLoader {
035:
036: private FunkyJar funky;
037:
038: /**
039: * Constructor.
040: *
041: * @param file
042: * <tt>File</tt> representing JAR file
043: * @param compress
044: * If <tt>true</tt>, contents of JAR file is compressed
045: * @param version
046: * Version for the JAR file's manifest
047: * @param author
048: * Author string from JAR file's manifest
049: */
050: public JarFileCommitter(final File file, final boolean compress,
051: final String version, final String author)
052: throws IOException {
053:
054: funky = new FunkyJar(file, compress, version, author);
055: }
056:
057: protected OutputStream outputStreamFor(final String name)
058: throws IOException {
059:
060: funky.newEntry(name);
061: return funky;
062: }
063:
064: public OutputStream outputStreamFor(final ClassInfo info)
065: throws IOException {
066: // This is funky. Recall that a JarOutputStream is also an output
067: // stream. So, we just return it. This is why we have to
068: // override the write, etc. methods.
069:
070: // Make a new entry based on the class name
071: final String name = info.name() + ".class";
072: return outputStreamFor(name);
073: }
074:
075: /**
076: * Signifies that we are finished with this <tt>JarFileCommitter</tt>.
077: */
078: public void done() throws IOException {
079: funky.done();
080: }
081: }
082:
083: /**
084: * We subclass JarOutputStream so that we can return an OutputStream to which a
085: * BLOATed class file will be written. In order to accomodate non-compression,
086: * we have to perform the checksum along the way. Bletch.
087: */
088: class FunkyJar extends JarOutputStream {
089:
090: private static final String MANIFEST = JarFile.MANIFEST_NAME;
091:
092: private static final String MANIFEST_DIR = "META-INF/";
093:
094: private static final CRC32 crc32 = new CRC32();
095:
096: private boolean compress;
097:
098: private JarEntry currEntry;
099:
100: private Size size;
101:
102: class Size {
103: long value = 0;
104: }
105:
106: /**
107: * Constructor.
108: */
109: public FunkyJar(final File file, boolean compress,
110: final String version, final String author)
111: throws IOException {
112: super (new FileOutputStream(file));
113:
114: this .compress = compress;
115:
116: if (compress) {
117: this .setMethod(ZipOutputStream.DEFLATED);
118: } else {
119: this .setMethod(ZipOutputStream.STORED);
120: }
121:
122: final Manifest manifest = new Manifest();
123: final Attributes global = manifest.getMainAttributes();
124: if (global.getValue(Attributes.Name.MANIFEST_VERSION) == null) {
125: global.put(Attributes.Name.MANIFEST_VERSION, version);
126: }
127:
128: if (global.getValue(new Attributes.Name("Created-By")) == null) {
129: global.put(new Attributes.Name("Created-By"), author);
130: }
131:
132: // Add directory for manifest
133: JarEntry entry = new JarEntry(FunkyJar.MANIFEST_DIR);
134: entry.setTime(System.currentTimeMillis());
135: entry.setSize(0); // Directories have size 0
136: entry.setCrc(0); // Checksum is 0
137: this .putNextEntry(entry);
138:
139: // Add manifest
140: entry = new JarEntry(FunkyJar.MANIFEST);
141: entry.setTime(System.currentTimeMillis());
142: if (!compress) {
143: // Have to compute checksum ourselves. Use an ugly anonymous
144: // inner class. Influenced by CRC32OutputStream in
145: // sun.tools.jar.Main. Please don't sue me. I have no money.
146: // Maybe you could give me a job instead. Of course, then I'd
147: // have money and you would sue me. Hmm.
148: final Size size = new Size();
149: FunkyJar.crc32.reset();
150: manifest.write(new OutputStream() {
151: public void write(final int r) throws IOException {
152: FunkyJar.crc32.update(r);
153: size.value++;
154: }
155:
156: public void write(final byte[] b) throws IOException {
157: FunkyJar.crc32.update(b, 0, b.length);
158: size.value += b.length;
159: }
160:
161: public void write(final byte[] b, final int off,
162: final int len) throws IOException {
163: FunkyJar.crc32.update(b, off, len);
164: size.value += len - off;
165: }
166: });
167: entry.setSize(size.value);
168: entry.setCrc(FunkyJar.crc32.getValue());
169: }
170: this .putNextEntry(entry);
171: manifest.write(this ); // Write the manifest to JAR file
172: this .closeEntry();
173: }
174:
175: public void newEntry(final String name) throws IOException {
176: makeDirs(name);
177:
178: currEntry = new JarEntry(name);
179: currEntry.setTime(System.currentTimeMillis());
180: if (compress) {
181: currEntry.setMethod(ZipEntry.DEFLATED);
182: } else {
183: currEntry.setMethod(ZipEntry.STORED);
184: }
185: this .putNextEntry(currEntry);
186: FunkyJar.crc32.reset();
187: this .size = new Size();
188: }
189:
190: private Set dirs;
191:
192: /**
193: * look at the path name specified by key and create zip entries for each
194: * directory level not already added.
195: */
196: private void makeDirs(final String key) throws IOException {
197: if (dirs == null) {
198: dirs = new HashSet();
199: }
200: int idx = 0;
201: int last = 0;
202: while ((last = key.indexOf('/', idx + 1)) != -1) {
203: final String aDir = key.substring(0, last + 1);
204: if (!dirs.contains(aDir)) {
205: dirs.add(aDir);
206: this .putNextEntry(new ZipEntry(aDir));
207: this .closeEntry();
208: }
209: idx = last;
210: }
211: }
212:
213: public void write(final int r) throws IOException {
214: super .write(r);
215:
216: if (!compress && (size != null)) {
217: FunkyJar.crc32.update(r);
218: size.value++;
219: }
220: }
221:
222: public void write(final byte[] b) throws IOException {
223: super .write(b);
224:
225: if (!compress && (size != null)) {
226: FunkyJar.crc32.update(b, 0, b.length);
227: size.value += b.length;
228: }
229: }
230:
231: public void write(final byte[] b, final int off, final int len)
232: throws IOException {
233: super .write(b, off, len);
234:
235: if (!compress && (size != null)) {
236: FunkyJar.crc32.update(b, off, len);
237: size.value += len - off;
238: }
239: }
240:
241: public void close() throws IOException {
242: // Okay, everythings is done. Set some values for the entry,
243: // cross your fingers, and run away.
244: if (!compress && (size != null)) {
245: currEntry.setSize(size.value);
246: currEntry.setCrc(FunkyJar.crc32.getValue());
247: }
248:
249: currEntry = null;
250: size = null;
251: this .closeEntry();
252:
253: // Note that we don't invoke the super class method.
254: }
255:
256: /**
257: * Signifies that we are finished with this <tt>JarFileCommitter</tt>.
258: */
259: public void done() throws IOException {
260: super.close();
261: }
262: }
|