001: package de.masters_of_disaster.ant.tasks.ar;
002:
003: import java.io.FilterOutputStream;
004: import java.io.OutputStream;
005: import java.io.IOException;
006:
007: /**
008: * The ArOutputStream writes an ar archive as an OutputStream.
009: * Methods are provided to put entries, and then write their contents
010: * by writing to this stream using write().
011: */
012: public class ArOutputStream extends FilterOutputStream {
013: /** Fail if a long file name is required in the archive or the name contains spaces. */
014: public static final int LONGFILE_ERROR = 0;
015:
016: /** Long paths will be truncated in the archive. Spaces are replaced by '_' */
017: public static final int LONGFILE_TRUNCATE = 1;
018:
019: /** GNU ar variant is used to store long file names and file names with spaced in the archive. */
020: public static final int LONGFILE_GNU = 2;
021:
022: /** BSD ar variant is used to store long file names and file names with spaced in the archive. */
023: public static final int LONGFILE_BSD = 3;
024:
025: protected int currSize;
026: protected int currBytes;
027: protected byte[] oneBuf;
028: protected int longFileMode = LONGFILE_ERROR;
029: protected boolean writingStarted = false;
030: protected boolean inEntry = false;
031:
032: public ArOutputStream(OutputStream os) throws IOException {
033: super (os);
034: if (null == os) {
035: throw new NullPointerException("os must not be null");
036: }
037: this .out.write(ArConstants.ARMAGIC, 0,
038: ArConstants.ARMAGIC.length);
039: this .oneBuf = new byte[1];
040: }
041:
042: public void setLongFileMode(int longFileMode) {
043: if (writingStarted) {
044: throw new IllegalStateException(
045: "longFileMode cannot be changed after writing to the archive has begun");
046: }
047: if (LONGFILE_GNU == longFileMode) {
048: throw new UnsupportedOperationException(
049: "GNU variant isn't implemented yet");
050: }
051: if (LONGFILE_BSD == longFileMode) {
052: throw new UnsupportedOperationException(
053: "BSD variant isn't implemented yet");
054: }
055: this .longFileMode = longFileMode;
056: }
057:
058: /**
059: * Put an entry on the output stream. This writes the entry's
060: * header record and positions the output stream for writing
061: * the contents of the entry. Once this method is called, the
062: * stream is ready for calls to write() to write the entry's
063: * contents. Once the contents are written, closeEntry()
064: * <B>MUST</B> be called to ensure that all buffered data
065: * is completely written to the output stream.
066: *
067: * @param entry The ArEntry to be written to the archive.
068: */
069: public void putNextEntry(ArEntry entry) throws IOException {
070: writingStarted = true;
071: if (inEntry) {
072: throw new IOException(
073: "the current entry has to be closed before starting a new one");
074: }
075: String filename = entry.getFilename();
076: if ((filename.length() >= ArConstants.NAMELEN)
077: && (longFileMode != LONGFILE_TRUNCATE)) {
078: throw new RuntimeException("file name \""
079: + entry.getFilename() + "\" is too long ( > "
080: + ArConstants.NAMELEN + " bytes )");
081: }
082: if (-1 != filename.indexOf(' ')) {
083: if (longFileMode == LONGFILE_TRUNCATE) {
084: entry.setFilename(filename.replace(' ', '_'));
085: } else {
086: throw new RuntimeException("file name \""
087: + entry.getFilename() + "\" contains spaces");
088: }
089: }
090:
091: byte[] headerBuf = new byte[ArConstants.HEADERLENGTH];
092: entry.writeEntryHeader(headerBuf);
093: this .out.write(headerBuf, 0, ArConstants.HEADERLENGTH);
094:
095: this .currBytes = 0;
096: this .currSize = (int) entry.getSize();
097: inEntry = true;
098: }
099:
100: /**
101: * Close an entry. This method MUST be called for all file
102: * entries that contain data. The reason is that we must
103: * pad an entries data if it is of odd size.
104: */
105: public void closeEntry() throws IOException {
106: if (!inEntry) {
107: throw new IOException("we are not in an entry currently");
108: }
109:
110: if (this .currBytes < this .currSize) {
111: throw new IOException("entry closed at '" + this .currBytes
112: + "' before the '" + this .currSize
113: + "' bytes specified in the header were written");
114: }
115:
116: if (1 == (this .currSize & 1)) {
117: this .out.write(ArConstants.PADDING, 0, 1);
118: }
119:
120: inEntry = false;
121: }
122:
123: /**
124: * Writes a byte to the current ar archive entry.
125: *
126: * This method simply calls write( byte[], int, int ).
127: *
128: * @param b The byte to write to the archive.
129: */
130: public void write(int b) throws IOException {
131: this .oneBuf[0] = (byte) b;
132: this .write(this .oneBuf, 0, 1);
133: }
134:
135: /**
136: * Writes bytes to the current ar archive entry.
137: *
138: * This method simply calls write( byte[], int, int ).
139: *
140: * @param wBuf The buffer to write to the archive.
141: */
142: public void write(byte[] wBuf) throws IOException {
143: this .write(wBuf, 0, wBuf.length);
144: }
145:
146: /**
147: * Writes bytes to the current ar archive entry. This method
148: * is aware of the current entry and will throw an exception if
149: * you attempt to write bytes past the length specified for the
150: * current entry.
151: *
152: * @param wBuf The buffer to write to the archive.
153: * @param wOffset The offset in the buffer from which to get bytes.
154: * @param numToWrite The number of bytes to write.
155: */
156: public void write(byte[] wBuf, int wOffset, int numToWrite)
157: throws IOException {
158: if (!inEntry) {
159: throw new IOException("we are not in an entry currently");
160: }
161:
162: if ((this .currBytes + numToWrite) > this .currSize) {
163: throw new IOException("request to write '" + numToWrite
164: + "' bytes exceeds size in header of '"
165: + this .currSize + "' bytes");
166: }
167:
168: if (numToWrite > 0) {
169: this.out.write(wBuf, wOffset, numToWrite);
170: this.currBytes += numToWrite;
171: }
172: }
173: }
|