diff --git a/src/com/webcodepro/shrinkit/io/LittleEndianByteOutputStream.java b/src/com/webcodepro/shrinkit/io/LittleEndianByteOutputStream.java new file mode 100644 index 0000000..0d1eaaa --- /dev/null +++ b/src/com/webcodepro/shrinkit/io/LittleEndianByteOutputStream.java @@ -0,0 +1,124 @@ +package com.webcodepro.shrinkit.io; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import com.webcodepro.shrinkit.CRC16; + +/** + * An OutputStream with helper methods to write little endian numbers + * and other Apple-specific tidbits. + * + * @author robgreene@users.sourceforge.net + */ +public class LittleEndianByteOutputStream extends OutputStream implements ByteConstants { + private OutputStream outputStream; + private long bytesWritten = 0; + private CRC16 crc = new CRC16(); + + /** + * Construct a LittleEndianByteOutputStream from an OutputStream. + */ + public LittleEndianByteOutputStream(OutputStream outputStream) { + this.outputStream = outputStream; + } + + /** + * Write a next byte. + */ + public void write(int b) throws IOException { + outputStream.write(b); + crc.update(b); + } + + /** + * Write the NuFile id to the LittleEndianByteOutputStream. + */ + public void writeNuFileId() throws IOException { + write(NUFILE_ID); + } + /** + * Write the NuFX id to the LittleEndianByteOutputStream. + */ + public void writeNuFxId() throws IOException { + write(NUFX_ID); + } + /** + * Write a "Word". + */ + public void writeWord(int w) throws IOException { + write(w & 0xff); + write(w >> 8); + } + /** + * Write a "Long". + */ + public void writeLong(long l) throws IOException { + write((int)(l & 0xff)); + write((int)((l >> 8) & 0xff)); + write((int)((l >> 16) & 0xff)); + write((int)((l >> 24) & 0xff)); + } + /** + * Write the Java Date object as a TimeRec. + * Note that years 2000-2039 are assumed to be 00-39 per the NuFX addendum + * at http://www.nulib.com/library/nufx-addendum.htm. + * @see http://www.nulib.com/library/nufx-addendum.htm + */ + public void writeDate(Date date) throws IOException { + byte[] data = null; + if (date == null) { + data = TIMEREC_NULL; + } else { + data = new byte[TIMEREC_LENGTH]; + GregorianCalendar gc = new GregorianCalendar(); + gc.setTime(date); + int year = gc.get(Calendar.YEAR); + year -= (year < 2000) ? 1900 : 2000; + data[TIMEREC_YEAR] = (byte)(year & 0xff); + data[TIMEREC_MONTH] = (byte)(gc.get(Calendar.MONTH) + 1); + data[TIMEREC_DAY] = (byte)gc.get(Calendar.DAY_OF_MONTH); + data[TIMEREC_HOUR] = (byte)gc.get(Calendar.HOUR_OF_DAY); + data[TIMEREC_MINUTE] = (byte)gc.get(Calendar.MINUTE); + data[TIMEREC_SECOND] = (byte)gc.get(Calendar.SECOND); + data[TIMEREC_WEEKDAY] = (byte)gc.get(Calendar.DAY_OF_WEEK); + } + write(data); + } + + /** + * Reset the CRC-16 to $0000. + */ + public void resetCrc() { + crc.reset(); + } + /** + * Get the current CRC-16 value. + */ + public long getCrcValue() { + return crc.getValue(); + } + + /** + * Answer with the total number of bytes written. + */ + public long getTotalBytesWritten() { + return bytesWritten; + } + + /** + * Pass the flush request to the wrapped stream. + */ + public void flush() throws IOException { + outputStream.flush(); + } + /** + * Pass the close request to the wrapped stream. + */ + public void close() throws IOException { + outputStream.close(); + } +} diff --git a/test_src/com/webcodepro/shrinkit/io/LittleEndianByteOutputStreamTest.java b/test_src/com/webcodepro/shrinkit/io/LittleEndianByteOutputStreamTest.java new file mode 100644 index 0000000..78c07bb --- /dev/null +++ b/test_src/com/webcodepro/shrinkit/io/LittleEndianByteOutputStreamTest.java @@ -0,0 +1,96 @@ +package com.webcodepro.shrinkit.io; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Calendar; +import java.util.GregorianCalendar; + +/** + * Exercise the LittleEndianByteOutputStream class. + * @author robgreene@users.sourceforge.net + */ +public class LittleEndianByteOutputStreamTest extends TestCaseHelper { + public void testWriteA() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + LittleEndianByteOutputStream bs = new LittleEndianByteOutputStream(os); + bs.write('a'); + bs.close(); + assertEquals("a".getBytes(), os.toByteArray()); + } + public void testWriteB() throws IOException { + // Just to ensure we can write chunks of bytes... + ByteArrayOutputStream os = new ByteArrayOutputStream(); + LittleEndianByteOutputStream bs = new LittleEndianByteOutputStream(os); + bs.write("hello".getBytes()); + bs.close(); + assertEquals("hello".getBytes(), os.toByteArray()); + } + public void testWriteNuFileId() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + LittleEndianByteOutputStream bs = new LittleEndianByteOutputStream(os); + bs.writeNuFileId(); + bs.close(); + assertEquals(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5 }, os.toByteArray()); + } + public void testCheckNuFxId() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + LittleEndianByteOutputStream bs = new LittleEndianByteOutputStream(os); + bs.writeNuFxId(); + bs.close(); + assertEquals(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xd8 }, os.toByteArray()); + } + public void testWriteWord() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + LittleEndianByteOutputStream bs = new LittleEndianByteOutputStream(os); + bs.writeWord(0x201); + bs.writeWord(0x403); + bs.close(); + assertEquals(new byte[] { 0x01, 0x02, 0x03, 0x04 }, os.toByteArray()); + } + public void testWriteWordHighBitSet() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + LittleEndianByteOutputStream bs = new LittleEndianByteOutputStream(os); + bs.writeWord(0xffff); + bs.close(); + assertEquals(new byte[] { (byte)0xff, (byte)0xff }, os.toByteArray()); + } + public void testWriteLong() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + LittleEndianByteOutputStream bs = new LittleEndianByteOutputStream(os); + bs.writeLong(0x04030201); + bs.close(); + assertEquals(new byte[] { 0x01, 0x02, 0x03, 0x04 }, os.toByteArray()); + } + public void testWriteLongHighBitSet() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + LittleEndianByteOutputStream bs = new LittleEndianByteOutputStream(os); + bs.writeLong(0xffffffffL); + bs.close(); + assertEquals(new byte[] { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff }, os.toByteArray()); + } + public void testWriteDate() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + LittleEndianByteOutputStream bs = new LittleEndianByteOutputStream(os); + bs.writeDate(new GregorianCalendar(1988, Calendar.OCTOBER, 22, 1, 10, 0).getTime()); + bs.writeDate(new GregorianCalendar(1988, Calendar.NOVEMBER, 17, 11, 16, 0).getTime()); + bs.writeDate(new GregorianCalendar(1988, Calendar.OCTOBER, 22, 13, 12, 0).getTime()); + bs.close(); + byte[] expected = new byte[] { + // From NuFX documentation, final revision 3 + 0x00, 0x0a, 0x01, 0x58, 0x16, 0x0a, 0x00, 0x07, // 01:10:00am 10/22/1988 saturday + 0x00, 0x10, 0x0b, 0x58, 0x11, 0x0b, 0x00, 0x05, // 11:16:00am 11/17/1988 thursday + 0x00, 0x0c, 0x0d, 0x58, 0x16, 0x0a, 0x00, 0x07, // 01:12:00pm 10/22/1988 saturday + }; + assertEquals(expected, os.toByteArray()); + } + public void testWriteNullDate() throws IOException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + LittleEndianByteOutputStream bs = new LittleEndianByteOutputStream(os); + bs.writeDate(null); + bs.close(); + byte[] expected = new byte[] { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // null date + }; + assertEquals(expected, os.toByteArray()); + } +}