001: /**
002: * JavaGuard -- an obfuscation package for Java classfiles.
003: *
004: * Copyright (c) 1999 Mark Welsh (markw@retrologic.com)
005: * Copyright (c) 2002 Thorsten Heit (theit@gmx.de)
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * The author may be contacted at theit@gmx.de.
022: *
023: *
024: * $Id: GuardDB.java,v 1.19 2002/06/04 10:01:05 glurk Exp $
025: */package net.sf.javaguard;
026:
027: import java.io.*;
028: import java.util.*;
029: import java.util.jar.*;
030: import java.security.DigestOutputStream;
031: import java.security.MessageDigest;
032: import java.security.NoSuchAlgorithmException;
033: import net.sf.javaguard.classfile.*;
034: import net.sf.javaguard.log.*;
035:
036: import org.apache.oro.text.MalformedCachePatternException;
037: import org.apache.oro.text.regex.MalformedPatternException;
038:
039: /** Classfile database for obfuscation.
040: *
041: * @author <a href="mailto:theit@gmx.de">Thorsten Heit</a>
042: * @author <a href="mailto:markw@retrologic.com">Mark Welsh</a>
043: */
044: public class GuardDB implements ClassConstants {
045: /** The file name of the manifest file. */
046: private static final String STREAM_NAME_MANIFEST = "META-INF/MANIFEST.MF";
047: /** Signature directory name. */
048: private static final String SIGNATURE_PREFIX = "META-INF/";
049: /** Signature file name extension. */
050: private static final String SIGNATURE_EXT = ".SF";
051:
052: /** Holds a list of input Jar files to obfuscate. */
053: private Vector inputJars = null;
054: /** Holds a list of input directories with files to obfuscate. The vector
055: * must have exact the same number of elements as the {@link #inputFileFilters}
056: * vector.
057: */
058: private Vector inputDirs = null;
059: /** Holds a list of file filter expressions for the input directories. The
060: * vector must have exact the same number of elements as the {@link #inputDirs}
061: * vector.
062: */
063: private Vector inputFileFilters = null;
064: /** Holds a list of output containers that each manage the output name of
065: * the obfuscated Jar file and the file containers that are used as input for
066: * the obfuscator.
067: */
068: private Vector outputContainers = null;
069: /** Holds the output file. */
070: private File outputFile;
071: /** Holds the tree of packages, classes, methods and fields. */
072: private ClassTree classTree = null;
073: /** Holds script file to use for the obfuscation. */
074: private ScriptFile scriptFile;
075: /** Holds the package, class, method and field mapping entries from the
076: * script file. */
077: private Vector mappings = null;
078:
079: /** Holds the log file to use. */
080: private FileLogger logfile;
081: /** Holds the system logger. */
082: private ScreenLogger logger;
083:
084: /** Default constructor. Builds a classfile database for obfuscation.
085: */
086: public GuardDB() {
087: logfile = FileLogger.getInstance();
088: logger = ScreenLogger.getInstance();
089: setScriptFile(null);
090: }
091:
092: /** Stores the script file to use for the obfuscation.
093: * @param scriptFile the script file to use for the obfuscation
094: */
095: public void setScriptFile(ScriptFile scriptFile) {
096: this .scriptFile = scriptFile;
097: }
098:
099: /** Returns the script file to use for the obfuscation.
100: * @return script file; may be null
101: * @see #setScriptFile
102: */
103: private ScriptFile getScriptFile() {
104: return scriptFile;
105: }
106:
107: /** Sets the list of input Jar files and directories files to obfuscate.
108: * @param jars a vector that holds the list of input Jar files to obfuscate
109: * @param dirs a vector that holds a list of input directories; must have the
110: * same size number of elements as the file filter vector
111: * @param filters a vector that holds a list of file filter expressions; must
112: * have the same number of elements as the directories vector
113: * @throws IllegalArgumentException if the directory and file filter vectors
114: * don't have the same size
115: */
116: public void setInput(Vector jars, Vector dirs, Vector filters)
117: throws IllegalArgumentException {
118: inputJars = jars;
119: if ((null == dirs && null != filters)
120: || (null != dirs && null == filters)
121: || (null != dirs && null != filters && dirs.size() != filters
122: .size())) {
123: throw new IllegalArgumentException(
124: "Directory and file filter vectors must have the same size");
125: }
126: inputDirs = dirs;
127: inputFileFilters = filters;
128: }
129:
130: /** Returns the list of input Jar files to obfuscate.
131: * @return list of input Jar files; always a valid vector (may be empty)
132: * @see #setInput
133: */
134: private Vector getInputJars() {
135: if (null == inputJars) {
136: inputJars = new Vector();
137: }
138: return inputJars;
139: }
140:
141: /** Returns the list of input directories that contain files to obfuscate.
142: * @return list of directories; always a valid vector (may be empty)
143: * @see #setInput
144: */
145: private Vector getInputDirs() {
146: if (null == inputDirs) {
147: inputDirs = new Vector();
148: }
149: return inputDirs;
150: }
151:
152: /** Returns the list of file filter expressions for the input directories.
153: * @return list of file filter expressions; always a valid vector (may be
154: * empty)
155: * @see #setInput
156: */
157: private Vector getInputFileFilters() {
158: if (null == inputFileFilters) {
159: inputFileFilters = new Vector();
160: }
161: return inputFileFilters;
162: }
163:
164: /** Sets the output Jar file.
165: * @param file the output Jar file; must not be null
166: */
167: public void setOutputFile(File file) {
168: outputFile = file;
169: }
170:
171: /** Returns the output Jar file.
172: * @return output Jar file; may be null
173: * @see #setOutputFile
174: */
175: private File getOutputFile() {
176: return outputFile;
177: }
178:
179: /** Reads all class files from the Jar files and the local directories and
180: * builds the classfile database.
181: * @param dump true if the parsed class tree should be dumped to the console
182: * before it is obfuscated; false else
183: * @throws IOException if an I/O error occurs
184: * @throws MalformedPatternException if an error occurs during the compilation
185: * of regular expressions
186: */
187: public void obfuscate(boolean dump) throws IOException,
188: MalformedPatternException {
189: logger.log("Building the file containers...");
190:
191: // first create the output container
192: OutputContainer oc = new OutputContainer(getOutputFile());
193:
194: // add the file containers for all Jar files
195: Vector jars = getInputJars();
196: for (int i = 0; i < jars.size(); i++) {
197: FileContainer fc = new JarFileContainer(new JarFile(
198: (File) jars.elementAt(i)));
199: oc.addFileContainer(fc);
200: }
201:
202: // add the file containers for all local directories
203: Vector dirs = getInputDirs();
204: Vector fileFilters = getInputFileFilters();
205: for (int i = 0; i < dirs.size(); i++) {
206: FileContainer fc = new LocalDirectoryFileContainer(
207: (File) dirs.elementAt(i), (String) fileFilters
208: .elementAt(i));
209: oc.addFileContainer(fc);
210: }
211:
212: // walk through all file sets and remove duplicate entries
213: Vector vec = oc.getFileContainers();
214: for (int i = 0; i < vec.size(); i++) {
215: FileContainer fc = (FileContainer) vec.elementAt(i);
216: for (int j = i + 1; j < vec.size(); j++) {
217: FileContainer other = (FileContainer) vec.elementAt(j);
218: other.removeDuplicates(fc);
219: }
220: }
221:
222: addOutputContainer(oc);
223: startObfuscate(getOutputContainers(), dump);
224: }
225:
226: /** Starts the obfuscation process for the given list of output containers.
227: * @param vec a vector that holds output containers; may not be null
228: * @param dump true if the parsed class tree should be dumped to the console
229: * before it is obfuscated; false else
230: * @throws IOException if an I/O error occurs
231: * @throws MalformedPatternException if an error occurs during the compilation
232: * of regular expressions
233: */
234: private void startObfuscate(Vector vec, boolean dump)
235: throws IOException, MalformedPatternException {
236: // obfuscate each output container
237: Iterator iter = vec.iterator();
238: while (iter.hasNext()) {
239: // first build a new class tree
240: ClassTree classTree = new ClassTree();
241: ClassFile.resetDangerHeader();
242: parseScriptFile(classTree);
243:
244: OutputContainer oc = (OutputContainer) iter.next();
245: // add all classes from the file containers to the class tree
246: Vector fileContainers = oc.getFileContainers();
247: logger.log("Building the class tree...");
248: for (int j = 0; j < fileContainers.size(); j++) {
249: FileContainer fc = (FileContainer) fileContainers
250: .elementAt(j);
251: logger.log(Log.INFO, "Reading the contents from '"
252: + fc.getName() + "'");
253: addClasses(classTree, fc, dump);
254: }
255:
256: // write out a log header
257: writeLogHeader(getOutputFile().getName(), fileContainers);
258: // generate the mapping table for the class tree
259: generateMappingTable(classTree);
260: // log the mapping table to the log file
261: writeMappingTable(classTree);
262:
263: // create the output stream for the obfuscated Jar file
264: JarOutputStream jos = new JarOutputStream(
265: new BufferedOutputStream(new FileOutputStream(oc
266: .getOutputFile())));
267: jos.setComment(Version.getJarComment());
268:
269: // obfuscate all entries in the file containers
270: for (int j = 0; j < fileContainers.size(); j++) {
271: FileContainer fc = (FileContainer) fileContainers
272: .elementAt(j);
273: obfuscateFiles(jos, classTree, fc, oc
274: .getManifestContainer());
275: }
276:
277: // Finally, write the new manifest file
278: JarEntry outEntry = new JarEntry(STREAM_NAME_MANIFEST);
279: jos.putNextEntry(outEntry);
280: DataOutputStream manifest = new DataOutputStream(
281: new BufferedOutputStream(jos));
282: oc.getManifestContainer().write(manifest);
283: jos.closeEntry();
284:
285: // finally close the output Jar file
286: jos.close();
287:
288: // finally print warnings
289: logfile.printMethodWarnings();
290: logfile.printWarnings();
291: }
292: }
293:
294: /** Go through database marking certain entities for retention, while
295: * maintaining polymorphic integrity.
296: * @param classTree the class tree to which the script file entries are
297: * applied
298: * @throws MalformedPatternException if an error occurs during the compilation
299: * of regular expressions
300: * @throws IllegalArgumentException if a script entry contains an illegal type
301: */
302: public void parseScriptFile(ClassTree classTree)
303: throws MalformedPatternException, IllegalArgumentException {
304: if (null != getScriptFile()) {
305: Iterator iterator = getScriptFile().iterator();
306: // Enumerate the entries in the script file
307: while (iterator.hasNext()) {
308: ScriptEntry entry = (ScriptEntry) iterator.next();
309:
310: switch (entry.getType()) {
311: case ScriptConstants.TYPE_ATTRIBUTE:
312: case ScriptConstants.TYPE_RENAME:
313: case ScriptConstants.TYPE_PRESERVE:
314: classTree.retainAttribute(entry);
315: break;
316:
317: case ScriptConstants.TYPE_PACKAGE:
318: case ScriptConstants.TYPE_PACKAGE_MAP:
319: case ScriptConstants.TYPE_CLASS:
320: case ScriptConstants.TYPE_CLASS_MAP:
321: case ScriptConstants.TYPE_METHOD:
322: case ScriptConstants.TYPE_METHOD_MAP:
323: case ScriptConstants.TYPE_FIELD:
324: case ScriptConstants.TYPE_FIELD_MAP:
325: addMapping(entry);
326: break;
327:
328: case ScriptConstants.TYPE_IGNORE:
329: classTree.addIgnoreDefaultRegex(entry.getName());
330: break;
331:
332: case ScriptConstants.TYPE_IGNORE_PACKAGE:
333: classTree.addIgnorePackageRegex(entry.getName());
334: break;
335:
336: case ScriptConstants.TYPE_IGNORE_CLASS:
337: classTree.addIgnoreClassRegex(entry.getName());
338: break;
339:
340: case ScriptConstants.TYPE_IGNORE_METHOD:
341: classTree.addIgnoreMethodRegex(entry);
342: break;
343:
344: case ScriptConstants.TYPE_IGNORE_FIELD:
345: classTree.addIgnoreFieldRegex(entry);
346: break;
347:
348: case ScriptConstants.TYPE_OBFUSCATE:
349: classTree.addObfuscateDefaultRegex(entry.getName());
350: break;
351:
352: case ScriptConstants.TYPE_OBFUSCATE_PACKAGE:
353: classTree.addObfuscatePackageRegex(entry.getName());
354: break;
355:
356: case ScriptConstants.TYPE_OBFUSCATE_CLASS:
357: classTree.addObfuscateClassRegex(entry.getName());
358: break;
359:
360: case ScriptConstants.TYPE_OBFUSCATE_METHOD:
361: classTree.addObfuscateMethodRegex(entry);
362: break;
363:
364: case ScriptConstants.TYPE_OBFUSCATE_FIELD:
365: classTree.addObfuscateFieldRegex(entry);
366: break;
367:
368: default:
369: // should never happen because we've already parsed the script
370: throw new IllegalArgumentException(
371: "Illegal type in script file");
372: }
373: }
374: }
375: }
376:
377: /** Adds the files from the given file container to the internal class
378: * database.
379: * @param classTree the class tree to which the available classes from the
380: * file container may be added
381: * @param fc the file container
382: * @param dump true if the parsed class tree should be dumped to the console
383: */
384: private void addClasses(ClassTree classTree, FileContainer fc,
385: boolean dump) {
386: Enumeration enumeration = fc.enumeration();
387: while (enumeration.hasMoreElements()) {
388: FileEntry entry = (FileEntry) enumeration.nextElement();
389: // only add the next entry if it is a class file
390: if (entry.isClassFile()) {
391: DataInputStream dis = entry.getInputStream();
392: ClassFile cf = null;
393: try {
394: cf = ClassFile.create(classTree, dis);
395: } catch (IOException ioex) {
396: logger.println("Error: " + ioex.getMessage());
397: logger.printStackTrace(ioex);
398: logfile.println("# ERROR - corrupt class file: "
399: + entry.getName());
400: logfile.printStackTrace(ioex);
401: } finally {
402: try {
403: dis.close();
404: } catch (IOException ioex) {
405: // ignored here
406: }
407: }
408:
409: // Check the class file for references to "dangerous" methods
410: cf.logDangerousMethods();
411: classTree.addClassFile(cf);
412: if (dump) {
413: PrintWriter pw = new PrintWriter(System.out);
414: cf.dump(pw);
415: //pw.close();
416: }
417: }
418: }
419: }
420:
421: /** Generates the mapping table for the obfuscation step.
422: * @param classTree the class tree for which the mapping table should be
423: * generated
424: * @throws MalformedPatternException If the compiled expression does not
425: * conform to the grammar understood by the PatternCompiler or if some other
426: * error in the expression is encountered.
427: */
428: private void generateMappingTable(ClassTree classTree)
429: throws MalformedPatternException {
430: logger
431: .log("Generating the mapping table for the whole class tree...");
432:
433: // before we generate obfuscated names we check which classes, fields etc.
434: // must be obfuscated and which should be ignored.
435: classTree.parseObfuscateAndIgnoreList();
436:
437: // now try to retain remote classes (if specified)
438: classTree.markRemoteClasses();
439:
440: // retain all marked mappings
441: classTree.retainMappings(getMappings());
442:
443: // retain additional fields, methods and classes
444: classTree.retainHardcodedReferences();
445: classTree.retainSerializableElements();
446:
447: // Traverse the class tree, generating obfuscated names within
448: // package and class namespaces
449: classTree.generateNames();
450:
451: // rename classes that need special treatment
452: classTree.retainRemoteClasses();
453:
454: // Resolve the polymorphic dependencies of each class, generating
455: // non-private method and field names for each namespace
456: classTree.resolveClasses();
457: }
458:
459: /** Obfuscate all entries in the file container.
460: * @param jos the Jar file output stream
461: * @param classTree the class tree the file container belongs to
462: * @param fileContainer the file container that holds all input files
463: * @param manifestContainer the manifest container that manages the Manifest
464: * file
465: * @throws IOException if an I/O error occurs
466: */
467: private void obfuscateFiles(JarOutputStream jos,
468: ClassTree classTree, FileContainer fileContainer,
469: ManifestContainer manifestContainer) throws IOException {
470: logger.log(Log.INFO,
471: "Obfuscating the entries in the file container: "
472: + fileContainer.getName());
473:
474: // Go through the file container, remove attributes and rename the constant
475: // pool for each class file. Other files are renamed only if the user
476: // wishes to do so, but apart from this they are copied through unchanged.
477: // Manifest and signature files are deleted, and a new Manifest file is
478: // generated.
479: Enumeration enumeration = fileContainer.enumeration();
480: while (enumeration.hasMoreElements()) {
481: FileEntry entry = (FileEntry) enumeration.nextElement();
482: // Open the entry and prepare to process it
483: DataInputStream inStream = entry.getInputStream();
484: try {
485: if (entry.isClassFile()) {
486: // Write the obfuscated version of the class to the output Jar
487: ClassFile cf = ClassFile
488: .create(classTree, inStream);
489: cf.remap(classTree);
490: logger.log(Log.DEBUG, "Reading: "
491: + entry.getName());
492: logger.log(Log.DEBUG, " -> writing "
493: + cf.getName() + CLASS_EXT);
494: JarEntry outEntry = new JarEntry(cf.getName()
495: + CLASS_EXT);
496: jos.putNextEntry(outEntry);
497:
498: // Create an OutputStream piped through a number of digest generators for the manifest
499: MessageDigest shaDigest = MessageDigest
500: .getInstance("SHA-1");
501: MessageDigest md5Digest = MessageDigest
502: .getInstance("MD5");
503: DataOutputStream classOutputStream = new DataOutputStream(
504: new DigestOutputStream(
505: new BufferedOutputStream(
506: new DigestOutputStream(jos,
507: shaDigest)),
508: md5Digest));
509:
510: // Dump the classfile, while creating the digests
511: cf.write(classOutputStream);
512: classOutputStream.flush();
513: jos.closeEntry();
514:
515: // Now update the manifest entry for the class with new name and new digests
516: MessageDigest[] digests = { shaDigest, md5Digest };
517: manifestContainer.updateManifest(entry.getName(),
518: cf.getName() + CLASS_EXT, digests);
519: } else {
520: // Copy the non-class entry (resource file)
521: long size = entry.getSize();
522: if (size != -1) {
523: byte[] bytes = new byte[(int) size];
524: inStream.readFully(bytes);
525: String outName = classTree
526: .getOutputFileName(entry.getName());
527: if (!entry.getName().equals(outName)) {
528: logfile.log(Log.VERBOSE,
529: "# renaming resource: "
530: + entry.getName() + " -> "
531: + outName);
532: }
533: logger.log(Log.DEBUG, "Reading: "
534: + entry.getName());
535: logger
536: .log(Log.DEBUG, " -> writing "
537: + outName);
538: JarEntry outEntry = new JarEntry(outName);
539: jos.putNextEntry(outEntry);
540:
541: // Create an OutputStream piped through a number of digest generators for the manifest
542: MessageDigest shaDigest = MessageDigest
543: .getInstance("SHA");
544: MessageDigest md5Digest = MessageDigest
545: .getInstance("MD5");
546: DataOutputStream dataOutputStream = new DataOutputStream(
547: new DigestOutputStream(
548: new BufferedOutputStream(
549: new DigestOutputStream(
550: jos, shaDigest)),
551: md5Digest));
552:
553: // Dump the data, while creating the digests
554: dataOutputStream.write(bytes, 0, bytes.length);
555: dataOutputStream.flush();
556: jos.closeEntry();
557:
558: // Now update the manifest entry for the entry with new name and new digests
559: MessageDigest[] digests = { shaDigest,
560: md5Digest };
561: manifestContainer.updateManifest(entry
562: .getName(), outName, digests);
563: }
564: }
565: } catch (NoSuchAlgorithmException nae) {
566: logger.println("Cannot find message digest algorithm:");
567: logger.println(nae.getMessage());
568: logger.printStackTrace(nae);
569: // ignore that
570: } finally {
571: if (inStream != null) {
572: inStream.close();
573: }
574: }
575: }
576: }
577:
578: /** Writes a header out to the log file.
579: * @param outName the name of the output Jar file
580: * @param vec a vector containing the file containers used for the obfuscation
581: */
582: private void writeLogHeader(String outName, Vector vec) {
583: logger.log(Log.INFO, "Writing log header...");
584: logfile
585: .println("# If this log is to be used for incremental obfuscation / patch generation, ");
586: logfile
587: .println("# add any '.class', '.method', '.field' and '.attribute' restrictions here:");
588: logfile.println();
589: logfile.println();
590: logfile
591: .println("#-DO-NOT-EDIT-BELOW-THIS-LINE------------------DO-NOT-EDIT-BELOW-THIS-LINE--");
592: logfile.println("#");
593: logfile.println("# JavaGuard Bytecode Obfuscator, version "
594: + Version.getVersion());
595: logfile.println("#");
596: logfile
597: .println("# Logfile created on "
598: + new Date().toString());
599: logfile.println("#");
600: if (null != vec) {
601: for (int i = 0; i < vec.size(); i++) {
602: FileContainer fc = (FileContainer) vec.elementAt(i);
603: logfile.println("# Input taken for obfuscation: "
604: + fc.getName());
605: }
606: }
607: logfile.println("# Output Jar file: " + outName);
608: logfile.println("# JavaGuard script file used: "
609: + (null != getScriptFile() ? getScriptFile().getName()
610: : "(none, defaults used)"));
611: logfile.println("#");
612: logfile.println();
613: }
614:
615: /** Write the obfuscation steps to the log file.
616: * @param classTree the class tree whose mapping table should be written
617: * @see ClassTree#dump
618: */
619: private void writeMappingTable(ClassTree classTree) {
620: // Write the memory usage at this point to the log file
621: Runtime rt = Runtime.getRuntime();
622: rt.gc();
623: logfile.println();
624: logfile.println("#");
625: logfile
626: .println("# Memory in use after class data structure built: "
627: + Long.toString(rt.totalMemory()
628: - rt.freeMemory()) + " bytes");
629: logfile
630: .println("# Total memory available : "
631: + Long.toString(rt.totalMemory()) + " bytes");
632: logfile.println("#");
633:
634: // Write the name frequency and name mapping table to the log file
635: logfile.println();
636: classTree.dump();
637: }
638:
639: /** Returns the vector that holds the package, class, method and field
640: * mapping entries from the script file.
641: * @return vector that contains the script file entries; always non-null
642: */
643: private Vector getMappings() {
644: if (null == mappings) {
645: mappings = new Vector();
646: }
647: return mappings;
648: }
649:
650: /** Adds an element to the list of mapping entries.
651: * @param entry the entry from the script file
652: * @see #getMappings
653: */
654: private void addMapping(ScriptEntry entry) {
655: getMappings().addElement(entry);
656: }
657:
658: /** Returns the vector that stores all output containers.
659: * @return vector with output containers; always non-null
660: * @see #addOutputContainer
661: */
662: private Vector getOutputContainers() {
663: if (null == outputContainers) {
664: outputContainers = new Vector();
665: }
666: return outputContainers;
667: }
668:
669: /** Adds a new output container to the list of all available output
670: * containers.
671: * @param oc the output container to add
672: * @see #getOutputContainers
673: */
674: private void addOutputContainer(OutputContainer oc) {
675: getOutputContainers().addElement(oc);
676: }
677:
678: /** A manager class for output containers. Each output container holds a list
679: * of file containers (that manage which input files may be put into the
680: * output file), a manifest container (that maintains the Manifest file for
681: * the output Jar file) and the assigned output file.
682: */
683: private static class OutputContainer {
684: /** A vector that stores all assigned file containes. */
685: private Vector fileContainers = new Vector();
686: /** Manages the Manifest file. */
687: private ManifestContainer manifestContainer = new ManifestContainer();
688: /** Holds the output file. */
689: private File outputFile;
690:
691: /** Creates a new output container and assignes it to the given output
692: * Jar file.
693: * @param file the output Jar file
694: */
695: OutputContainer(File file) {
696: this .outputFile = file;
697: }
698:
699: /** Adds a new file container to the internal list of file containers.
700: * @param fc the file container to add
701: * @see #getFileContainers
702: * @throws IOException if an I/O error occurs
703: */
704: void addFileContainer(FileContainer fc) throws IOException {
705: getFileContainers().addElement(fc);
706: getManifestContainer().addManifest(fc.getManifest());
707: }
708:
709: /** Returns the vector that contains all assigned file containers.
710: * @return vector with file containers
711: * @see #addFileContainer
712: */
713: Vector getFileContainers() {
714: return fileContainers;
715: }
716:
717: /** Returns the manifest container assigned to this object.
718: * @return manifest container
719: */
720: ManifestContainer getManifestContainer() {
721: return manifestContainer;
722: }
723:
724: /** Returns the output file assigned to this object.
725: * @return the output file assigned to this object
726: */
727: File getOutputFile() {
728: return outputFile;
729: }
730: }
731: }
|