package de.masters_of_disaster.ant.tasks.ar; import java.io.FilterOutputStream; import java.io.OutputStream; import java.io.IOException; /** * The ArOutputStream writes an ar archive as an OutputStream. * Methods are provided to put entries, and then write their contents * by writing to this stream using write(). */ public class ArOutputStream extends FilterOutputStream { /** Fail if a long file name is required in the archive or the name contains spaces. */ public static final int LONGFILE_ERROR = 0; /** Long paths will be truncated in the archive. Spaces are replaced by '_' */ public static final int LONGFILE_TRUNCATE = 1; /** GNU ar variant is used to store long file names and file names with spaced in the archive. */ public static final int LONGFILE_GNU = 2; /** BSD ar variant is used to store long file names and file names with spaced in the archive. */ public static final int LONGFILE_BSD = 3; protected int currSize; protected int currBytes; protected byte[] oneBuf; protected int longFileMode = LONGFILE_ERROR; protected boolean writingStarted = false; protected boolean inEntry = false; public ArOutputStream(OutputStream os) throws IOException { super(os); if (null == os) { throw new NullPointerException("os must not be null"); } this.out.write(ArConstants.ARMAGIC,0,ArConstants.ARMAGIC.length); this.oneBuf = new byte[1]; } public void setLongFileMode(int longFileMode) { if (writingStarted) { throw new IllegalStateException("longFileMode cannot be changed after writing to the archive has begun"); } if (LONGFILE_GNU == longFileMode) { throw new UnsupportedOperationException("GNU variant isn't implemented yet"); } if (LONGFILE_BSD == longFileMode) { throw new UnsupportedOperationException("BSD variant isn't implemented yet"); } this.longFileMode = longFileMode; } /** * Put an entry on the output stream. This writes the entry's * header record and positions the output stream for writing * the contents of the entry. Once this method is called, the * stream is ready for calls to write() to write the entry's * contents. Once the contents are written, closeEntry() * <B>MUST</B> be called to ensure that all buffered data * is completely written to the output stream. * * @param entry The ArEntry to be written to the archive. */ public void putNextEntry(ArEntry entry) throws IOException { writingStarted = true; if (inEntry) { throw new IOException("the current entry has to be closed before starting a new one"); } String filename = entry.getFilename(); if ((filename.length() >= ArConstants.NAMELEN) && (longFileMode != LONGFILE_TRUNCATE)) { throw new RuntimeException("file name \"" + entry.getFilename() + "\" is too long ( > " + ArConstants.NAMELEN + " bytes )"); } if (-1 != filename.indexOf(' ')) { if (longFileMode == LONGFILE_TRUNCATE) { entry.setFilename(filename.replace(' ','_')); } else { throw new RuntimeException("file name \"" + entry.getFilename() + "\" contains spaces"); } } byte[] headerBuf = new byte[ArConstants.HEADERLENGTH]; entry.writeEntryHeader(headerBuf); this.out.write(headerBuf,0,ArConstants.HEADERLENGTH); this.currBytes = 0; this.currSize = (int) entry.getSize(); inEntry = true; } /** * Close an entry. This method MUST be called for all file * entries that contain data. The reason is that we must * pad an entries data if it is of odd size. */ public void closeEntry() throws IOException { if (!inEntry) { throw new IOException("we are not in an entry currently"); } if (this.currBytes < this.currSize) { throw new IOException("entry closed at '" + this.currBytes + "' before the '" + this.currSize + "' bytes specified in the header were written"); } if (1 == (this.currSize & 1)) { this.out.write(ArConstants.PADDING,0,1); } inEntry = false; } /** * Writes a byte to the current ar archive entry. * * This method simply calls write( byte[], int, int ). * * @param b The byte to write to the archive. */ public void write(int b) throws IOException { this.oneBuf[0] = (byte) b; this.write(this.oneBuf, 0, 1); } /** * Writes bytes to the current ar archive entry. * * This method simply calls write( byte[], int, int ). * * @param wBuf The buffer to write to the archive. */ public void write(byte[] wBuf) throws IOException { this.write(wBuf, 0, wBuf.length); } /** * Writes bytes to the current ar archive entry. This method * is aware of the current entry and will throw an exception if * you attempt to write bytes past the length specified for the * current entry. * * @param wBuf The buffer to write to the archive. * @param wOffset The offset in the buffer from which to get bytes. * @param numToWrite The number of bytes to write. */ public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException { if (!inEntry) { throw new IOException("we are not in an entry currently"); } if ((this.currBytes + numToWrite) > this.currSize) { throw new IOException("request to write '" + numToWrite + "' bytes exceeds size in header of '" + this.currSize + "' bytes"); } if (numToWrite > 0) { this.out.write(wBuf,wOffset,numToWrite); this.currBytes += numToWrite; } } }